CountDownLatch是一个同步协助类,通常用于一个或多个线程等待,直到其他线程完成某项工作。
CountDownLatch使用一个计数值进行初始化,调用它提供的await()方法的线程会被阻塞直到该计数值减为0。减计数值的方法是countDown(),该方法可以在同一个线程中多次调用,也可以在多个线程中被调用,当计数值减为0时所有调用await()方法的线程被唤醒。
CountDownLatch的构造函数定义如下,count的值被赋值给state。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch的常用方法如下:
//当前线程阻塞,直到count/state的值变为0
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//当前线程阻塞,直到count/state的值变为0或等待timeout的时间
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//将count/state的值减1
public void countDown() {
sync.releaseShared(1);
}
CountDownLatch一般可以用于两个场景:
这种场景下,通常是多个线程调用await()方法阻塞,直到其他线程调用countDownLatch()将count的值减为0,如下:
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
//准备完毕……运动员都阻塞在这,等待号令
countDownLatch.await();
String parter = "【" + Thread.currentThread().getName() + "】";
System.out.println(parter + "开始执行……");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000);// 裁判准备发令
countDownLatch.countDown();// 发令枪:执行发令
}
在很多场景中,主流程需要等待多个不同的任务完成后再处理结果,此时就要求主流程线程需要阻塞直到所有任务执行完成,如下:
public class CountDownLatchTest {
private static volatile int count = 0;
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int index = i;
new Thread(() -> {
try {
Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
count++;
System.out.println(Thread.currentThread().getName()+" finish task" + index );
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 主线程在阻塞,当计数器==0,就唤醒主线程往下执行。
countDownLatch.await();
System.out.println("所有任务执行完成,count=" + count);
}
}
这里主要介绍最常用的await()和countDown()方法的源码,以及CountDownLatch的原理。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
//如果state=0,则返回正值1,否则返回负值-1
return (getState() == 0) ? 1 : -1;
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":我阻塞了...");
latch.await();
System.out.println(Thread.currentThread().getName() + ":我被唤醒了...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"thread-branch").start();
Thread.sleep(100);
System.out.println("调用countDown()方法前,count=" + latch.getCount());
latch.countDown();
Thread.sleep(100);
System.out.println("调用countDown()方法后,count=" + latch.getCount());
latch.await();
System.out.println("这里如果执行了,说明主线程没有阻塞,count=" + latch.getCount());
}
countDown()方法的主要作用是将state减1,当state=0时则需要唤醒所有在同步等待队列中阻塞的线程。
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
//判断如果state=0,则不需要再唤醒同步等待队列了,因为之前已经唤醒过了
if (c == 0)
return false;
//这里就是countDown()方法的主要作用,将state-1
int nextc = c-1;
//如果使用CAS修改state-1失败,则循环修改直到成功
if (compareAndSetState(c, nextc))
//修改state成功后,如果state=0,则返回true
return nextc == 0;
}
}
CountDownLatch的使用流程可以总结如下:
从上面可以看出CountDownLatch和Semaphore都是通过AQS的共享锁实现的,虽然它们的实现效果截然不同,但是比较它们的不同可以帮助我们记忆它们各自的实现。
以上就决定了Semaphore的作用是限流,CountDownLatch的作用是协助线程同步执行。
需要注意的是,CountDownLatch只能使用一次,即当调用countDown()将state减为0后,当前CountDownLatch对象就没用了。如果想要达到重复使用的目的,可以选择另一个功能较CountDownLatch更强大的CyclicBarrier。