倒数闩锁CountDownLatch源码浅析
1 前言
CountDownLatch
是一种同步辅助工具类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。(源码分析基于JDK1.8) CountDownLatch需要用给定的闩锁计数count初始化。await
方法使当前线程阻塞(每执行一次countDown
方法就将闩锁计数减1),直到闩锁计数达到零时(所有因此阻塞等待的线程都)才会被唤醒。CountDownLatch是一次性使用的同步工具,闩锁计数无法重置,如果需要重置计数,可能使用CyclicBarrier
更合适。
2 用法示例
我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用join()方法.
public class JoinCountDownLatchTest { public static void main(String[] args) throws InterruptedException { Thread parser1 = new Thread(new Runnable() { @Override public void run() { System.out.println("parser1 finish"); } }); Thread parser2 = new Thread(new Runnable() { @Override public void run() { System.out.println("parser2 finish"); } }); parser1.start(); parser2.start(); parser1.join(); parser2.join(); System.out.println("all parser finish"); } }
CountDownLatch
可以实现join类似的功能,但它更强大,它提供了很多API方法,能够实现更精准的控制。
CountDownLatch的构造方法必须传入一个int类型的参数,这个参数作为闩锁的计数器。
CountDownLatch的countDown
和await
方法一般都要配合使用。await
方法(休眠)阻塞当前线程,而每调用一次countDown
方法,闩锁计数就减1,当其减为0时,当前线程就被唤醒、await方法得以返回。
class CountDownLatchTest { static CountDownLatch c = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { Thread parser1 = new Thread(() -> { System.out.println("parser1 finish"); c.countDown(); }); Thread parser2 = new Thread(new Runnable() { @Override public void run() { System.out.println("parser2 finish"); c.countDown(); } }); parser1.start(); parser2.start(); c.await(); System.out.println("all parser finish"); } }
打印结果:
parser1 finish
parser2 finish
all parser finish
若c.await();
被注释掉,就不能保证打印的先后顺序,输出结果如下:
all parser finish
parser1 finish
parser2 finish
2) 示例2
这里有两个类Driver和Worker,分别表示驱动者、工作者线程。这里使用了两个CountDownLatch对象,第一个表示启动信号,可防止任何工作者线程Worker前进处理,直到驱动者Driver为它们做好准备为止;第二个表示完成信号,允许驱动者Driver等到所有工作者线程Worker都完成任务为止。
class Driver { // ... void main() throws InterruptedException { CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don't let run yet 做准备 startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } }
3 实现分析
CountDownLatch的实现主要基于同步器AbstractQueuedSynchronizer
,它利用AQS实现了一个共享锁. CountDownLatch主要有一个Sync
类型成员变量sync, Sync
是继承抽象类AbstractQueuedSynchronizer的静态内部类。
private final Sync sync;
1) 构造方法CountDownLatch(int)
CountDownLatch的构造方法主要是执行this.sync = new Sync(count)
对sync进行实例化, 而Sync(int)
又将父类AbstractQueuedSynchronizer
的实例变量state
设置为指定的count。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count);//实例化 } Sync(int count) { setState(count);//将`AbstractQueuedSynchronizer`的state设为count }
2) 静态内部类Sync
Sync主要重写了父类的tryAcquireShared
、tryReleaseShared
方法,这两个方法都是实现共享锁所必须重写的相关方法,其作用分别是尝试获取共享状态、尝试释放共享状态,两者刚好配对。有关AQS详细分析,请看之前的博客AbstractQueuedSynchronizer实现原理分析。
protected int tryAcquireShared(int acquires) { //state为0,闩锁计数为0 ,返回1,获取共享状态成功 //反之闩锁计数不为0,返回-1,获取共享状态失败。 return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) //state已经为0,非法状态返回false.(只有在已获取锁,即state非零时,才有释放锁的说法) return false; int nextc = c-1; if (compareAndSetState(c, nextc))//cas自旋将state减1 return nextc == 0;//state自减1后为零,返回true,可释放锁。反之返回false,还不能释放锁。 } }
另外Sync还提供了一个方法getCount
,返回当前剩余的闩锁计数,它直接调用父类AQS的getState
实现。
int getCount() { return getState(); }
3) await
await使当前线程休眠等待,直到count减少至0或线程中断。
await调用了AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly获取共享锁并响应中断。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
await(long , TimeUnit )
是await()的超时版本。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
4) countDown
countDown将闩锁计数递减1,若递减后为0就将唤醒所有阻塞等待的线程。如果闩锁的计数(递减前)已经为零,就啥也不做,恰好与上面tryReleaseShared
方法体中的if (c == 0) return false;
所对应。
public void countDown() { sync.releaseShared(1); }
5) getCount
getCount用于查询当前的闩锁计数
public long getCount() { return sync.getCount(); }