一、CountDownLatch能做什么
CountDownLatch是java.util.concurrent包中的一个类,它主要用来协调多个线程之间的同步,起到一个同步器的作用。总的来说,CountDownLatch让一个或多个线程在运行过程中的某个时间点能停下来等待其他的一些线程完成某些任务后再继续运行。
类似的任务可以使用线程的 join() 方法实现:在等待时间点调用其他线程的 join() 方法,当前线程就会等待join线程执行完之后才继续执行,但 CountDownLatch 实现更加简单,并且比 join 的功能更多。
二、CountDownLatch怎么用
CountDownLatch的构造函数接收一个 int 型参数作为计数器,例如想让N任务完成之后才能继续执行,创建CountDownLatch时传入参数N;
需要等待的线程会调用CountDownLatch的await方法,该线程就会阻塞直到计数器的值减为0,而达到自己预期的线程会调用CountDownLatch的countDown()方法,计数器N的值减1。由于countDown方法可以在任何地方调用,所以计数器N既可以代表N个线程,也可以是一个线程的N个执行步骤。代表多个线程时,只需要将这个CountDownLatch的引用传到线程里面即可。
N的值为1时,退化为单一事件,即由一个线程来通知其他线程,效果等同于对象的wait和notifyAll;N为0时,调用await方法不会阻塞当前线程。
上图中,等待线程为4和5,计数器的值初始化为3,当线程1,2,3都完成调用countDown()方法后,线程4,5才会从await()方法返回,继续执行后面的代码。
综上,使用CountDownLatch有三个主要步骤
首先创建CountDownLatch实例时通过构造函数指定计数器的值N;
然后需要等待的线程调用CountDownLatch实例的await方法,等待计数器的值将为0;
被等待的线程在任务完成时调用CountDownLatch实例的countDown方法,计数器的值减一。
一个例子:有三个线程解析表格中的数据,主线程需要等到表格解析完成后才执行后面的操作
public class CountDownLatchTest {
public static void main(String[] args) throws Exception{
/*创建CountDownLatch实例,计数器的值初始化为3*/
final CountDownLatch downLatch = new CountDownLatch(3);
/*创建三个线程,每个线程等待1s,表示执行比较耗时的任务*/
for(int i = 0;i < 3;i++){
final int num = i;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(String.format("thread %d has finished",num));
/*任务完成后调用CountDownLatch的countDown()方法*/
downLatch.countDown();
}
}).start();
}
/*主线程调用await()方法,等到其他三个线程执行完后才继续执行*/
downLatch.await();
System.out.print("all threads have finished,main thread will continue run");
}
}
运行结果:
三、CountDown 源码分析
分析CountDownLatch的源码我们可以知道,它是使用了一个内部同步器AQS来实现屏蔽功能的。只有当count的值为零时,同步器的tryAcquireShared的结果为1,其他时候都是-1。详细代码如下:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
四、CountDownLatch 的不足
CountDownLatch是一次性的,不可能重新初始化或者修改其内部计数器的值,当CountDownLatch使用完毕后,它不能再次被使用。