公平的自旋锁

  之前我们了解了自旋锁(见自旋锁浅析),现在来看看怎么让自旋锁变得公平。何谓公平?假如现在有10个线程来抢锁,按顺序排队,0号线程是第一个,9号线程是最后一个,把锁从0号依次传递到9号,这就是公平的。反之,不按先来后到的顺序来,就是不公平的。

  那么怎么实现公平?结合现实生活中的例子,我们去银行或者医院这些地方,首先要做的事情就是取号,然后等待叫号,轮到你了就去窗口办理。后面来的人继续取号,号码累加,而系统按先来后到一个号接一个号处理,只管按自己的号来叫。很明显,这种模式就是公平的,我们的自旋锁可借鉴此种模式来实现:

package com.wulf.test.testpilling.util;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 公平的自旋锁
 *
 * @author wulf
 * @since 2018年12月26日*/
public class FairSpinLock implements Lock
{
    // 通过AtomicInteger实现CAS,用户排队号,初始0
    private AtomicInteger userNo = new AtomicInteger();
    
    // 通过AtomicInteger实现CAS,系统叫号,初始1
    private AtomicInteger sysNo = new AtomicInteger(1);
    
    // 存储用户排队号,各个线程均可见
    private ThreadLocal<Integer> userNoHolder = new ThreadLocal<Integer>();
    
    @Override
    public void lock()
    {
        // 获取到锁,初始0,自增后此处为1,后续继续自增,把当前的用户号存好,若此时用户号=系统号则获得锁
        Integer currentUserNo = userNo.incrementAndGet();
        
        // 设置到线程副本中
        userNoHolder.set(currentUserNo);
        
        // 无限循环,判断系统叫号与当前用户排队号是否相同
        while (currentUserNo != sysNo.get())
        {
        }
    }
    
    @Override
    public void unlock()
    {
        Integer currentUserNo = userNoHolder.get();
        
        // 释放锁时,把存起来的用户号+1(获取锁时用户号=系统号,所以用户号+1=系统号+1),存到系统号去
        sysNo.compareAndSet(currentUserNo, currentUserNo + 1);
    }
    
    @Override
    public void lockInterruptibly()
        throws InterruptedException
    {
        // TODO Auto-generated method stub
        
    }
    
    @Override
    public boolean tryLock()
    {
        // TODO Auto-generated method stub
        return false;
    }
    
    @Override
    public boolean tryLock(long time, TimeUnit unit)
        throws InterruptedException
    {
        // TODO Auto-generated method stub
        return false;
    }
    
    @Override
    public Condition newCondition()
    {
        // TODO Auto-generated method stub
        return null;
    }
    
}

  继续用老例子测试:

  @Test
    public void testFairSpinLock()
    {
        // 初始化自旋锁
        FairSpinLock fsl = new FairSpinLock();
        
        for (int i = 0; i < 10; i++)
        {
            new Thread(() -> {
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        // 加锁
                        fsl.lock();
                        
                        // 自增
                        count++;
                        
                        // 解锁
                        fsl.unlock();
                    }
                    ;
                    
                    // 一个线程执行完了就减1,10个线程执行完了就变成0,执行主线程
                    latch.countDown();
                }
            }).start();
        }
        
        // 主线程等待
        try
        {
            latch.await();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        
        TestCase.assertEquals(count, 100000);
    }

  输出:

count值:10000, 耗时:6毫秒.

 

posted on 2018-12-26 19:50  不想下火车的人  阅读(827)  评论(0编辑  收藏  举报

导航