公平的自旋锁
之前我们了解了自旋锁(见自旋锁浅析),现在来看看怎么让自旋锁变得公平。何谓公平?假如现在有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毫秒.