CountDownLatch简笔
前言
JDK的并发包java.util.concurrent中提供了并发编程&多线程编程非常有用的工具类。本文基于CountDownLatch的源代码,介绍该类的使用及原理。
CountDownLatch介绍
CountDownLatch也叫做闭锁,根据其英文名称,可以理解为数量递减门栓。就是说门栓有个标记量,该标记量为0时门栓打开,线程可以继续执行;该标记大于0时门栓是关闭状态,所有线程等待。
官方解释是:一个同步工具,该同步工具允许一个或者多个线程等待一系列的操作被其他线程执行完成。
该类有如下方法:#await();#await(long TimeUnit);#countDown();
CountDownLatch类初始化时给定一个正整数计数器count,代表改锁要控制的线程数。调用await方法阻塞当前线程,直到调用countDown(调用该方法,计数器count值减1)方法将内部count减为0时,所有等待的线程被唤醒执行。同时接下来调用await方法会立即返回。这里有一个点要注意:计数器count的值不能被重置,如果需要重置,考虑使用CyclicBarrier类。
CountDownLatch作为同步工具功能丰富。其被初始化时传入一个计数器count作为简便门栓或大门的开关:所有线程调用CountDownLatch的await方法等待大门的打开(线程被阻塞),直到有线程调用其countDown方法。CoundDownLatch初始化时计数器为N,可表示一个线程等待N个线程完成某些操作或者某些操作被做了N次。
CountDownLatch一个有用的特性是它不需要线程在处理之前就调用countDown来等待计数器count的值减为0,它只是在所有线程都可以继续执行时通过调用await方法阻止其他线程继续执行。
使用场景
1、等待问题
一组工作线程中的两个类来使用两个闭锁。第一个CountDownLatch作为一个启动信号量,只有等Driver准备好了某些操作才能让所有Worker继续处理,否则所有Worker只能一直等待。第二个CountDownLatch作为一个结束信号量,让所有Workder都处理完成后Driver才能继续处理,否则就一直等待。归结为一句话就是启动信号量就是N个线程等待一个线程处理完,结束信号量就是一个线程等待N个线程处理完。
class Driver {
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i)
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() { ... }
}}
可以看到Driver的main函数启动了Worker的线程后,立即调用开始信号量startSignal的await方法,等待Driver的主线程的doSomethingElse()方法做完某些处理后调用startSignal的countDown方法;此时的这个场景就是N个线程等待一个线程。Driver中main方法继续调用doSomethingElse()方法,然后调用结束信号量doneSignal的await方法,等待Worker线程的调用doneSignal的countDown方法,Worker中的线程被唤醒后,执行doWork方法,然后执行doneSignal的countDown方法,最终doneSignal的count置位0后唤醒Driver的主线程。此时的场景就是一个线程等待N个线程。
2、分而治之
CountDownLatch另一个典型的用法是将一个问题切分成N部分,用Runnable来描述每一部分并执行每一部分,同时减少门栓数量(countDown方法),将这些Runnable加入到Executor中。当所有的Runnable执行完成后等待线程才允许通过继续执行(当线程必须以这种方式重复减少门栓数量即调用countDown方法时,考虑使用CyclicBarrier)。
class Driver2 {
void main() throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
Executor e = ...
for (int i = 0; i < N; ++i) // create and start threads
e.execute(new WorkerRunnable(doneSignal, i));
doneSignal.await(); // wait for all to finish
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
try {
doWork(i);
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
内存一致性
由于CountDownLatch是针对多线程流程控制的,所以在一个线程中调用countDown方法之前的操作(A)与在另外的线程中调用相应的await方法返回后的操作(B)符合happen-before关系。前者总是发生在后者之前,也就是A总是发生于B之前。
源码分析
CountDownLatch包含内部静态类Sync,该类继承了类AbstractQueuedSynchronizer(简称AQS),使用AQS的state来代表
CountDownLatch的count。
CountDownLatch包含的方法有countDown(),await(),await(long timeout, TimeUnit unit),getCount()。
- AbstractQueuedSynchronizer