一、CountDownLatch介绍
在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在 CountDownLatch 出现之前一般都使用线程的join()方法来实现这一点,但是 join 方法不够灵活,不能够满足不同场景的需要,所以JDK开发组提供了 CountDownLatch 这个类,使用CountDownLatch 会更优雅。并且CountDownLatch 可以在子线程的任何位置让 await 方法返回而不一定必须线程结束,这比join更加灵活。使用 CountDownLatch 的代码如下:
public static void main(String[] args) throws InterruptedException {
//内部sync同步器的state值设为3
CountDownLatch countDownLatch = new CountDownLatch(3);
for (int i = 1;i <= 3;i++) {
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("目标代码块运行完毕");
//每调用一次countDown()内部调用releaseShared(1)都会使state值减一
countDownLatch.countDown();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
}).start();
}
System.out.println("所有线程已启动");
//主线程调用await()方法后就会进入阻塞,直到所有线程调用了countDown()使state值被减为0,或者主线程被中断,才会从阻塞返回
countDownLatch.await();
System.out.println("所有线程目标代码块已运行完毕");
}
二、CountDownLatch原理
CountDownLatch类中使用了一个继承自AQS的共享锁Sync对象,构造CountDownLatch对象时会将传入的线程数值设为AQS的state值
-
当一个线程结束运行调用countDown()方法时,内部调用releaseShared(1)时调用Sync中重写的钩子方法tryReleaseShared()都会使state值减一,当被state被减为0后tryReleaseShared才会返回true,继而调用doReleaseShared唤醒同步队列中被阻塞的线程
-
主线程调用await()方法后就会进入阻塞,其内部调用的acquireSharedInterruptibly方法,只有当钩子方法tryAcquireShared返回值>=0时才能拿到锁,而Sync中重写的tryAcquireShared只有当state为0才会返回1,否则返回-1,因此只有到最后一个线程调用了countDown()使state值被减为0继而唤醒同步队列中阻塞的主线程,或者主线程被中断,才会从阻塞返回。