CountDownLatch介绍

  • CountDownLatch是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作。
  • CountDownLatch是一种公平锁的实现
  • CountDownLatch使用给定的计数值(count)初始化。await方法会阻塞直到当前的计数值(count)由于countDown方法的调用达到0,count为0之后所有等待的线程都会被释放,并且随后对await方法的调用都会立即返回

03-24 AQS之CountDownLatch - 图1

CountDownLatch应用场景

CountDownLatch一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。

场景1 让多个线程等待:模拟并发,让并发线程一起执行

  1. CountDownLatch countDownLatch = new CountDownLatch(1);
  2. for (int i = 0; i < 5; i++) {
  3. new Thread(() -> {
  4. try {
  5. //准备完毕……运动员都阻塞在这,等待号令
  6. countDownLatch.await();
  7. String parter = "【" + Thread.currentThread().getName() + "】";
  8. System.out.println(parter + "开始执行……");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }).start();
  13. }
  14. Thread.sleep(2000);// 裁判准备发令
  15. countDownLatch.countDown();// 发令枪:执行发令

await执行流程:

  • 每个线程执行await方法,实际调用acquireSharedInterruptibly方法,arg=1

截屏2022-03-24 23.17.10.png

  • CountDownLath的tryAcquireShared的实现是:当state不为0,都是返回-1,也就是有资源的时候都会调用doAcquireSharedInterruptibly

截屏2022-03-24 23.17.48.png

  • 而doAcquireSharedInterruptibly,也就是和AQS的方法一样,初始化等待队列,入队,然后把节点的waitState设置为-1,也就是next节点可以被唤醒。

countDown流程:

  • 实际调用releaseShared,arg=1

截屏2022-03-24 23.25.44.png

  • 在tryReleaseShared里面,会通过cas把state的值-1,只有state-1后的值是0才会返回true。如果最后的state=0,也就是所有的线程都准备好了,然后执行doReleaseShared。

截屏2022-03-24 23.27.32.png

  • 此时的head节点的waitState是-1,所以会进入到unparkSuccessor去唤醒线程

截屏2022-03-24 23.31.44.png

场景2 让单个线程等待:多个线程(任务)完成后,进行汇总合并

  1. CountDownLatch countDownLatch = new CountDownLatch(5);
  2. for (int i = 0; i < 5; i++) {
  3. final int index = i;
  4. new Thread(() -> {
  5. try {
  6. Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
  7. System.out.println(Thread.currentThread().getName()+" finish task" + index );
  8. countDownLatch.countDown();
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }).start();
  13. }
  14. // 主线程在阻塞,当计数器==0,就唤醒主线程往下执行。
  15. countDownLatch.await();
  16. System.out.println("主线程:在所有任务运行完成后,进行结果汇总");

CountDownLatch实现原理

  • 底层基于 AbstractQueuedSynchronizer 实现,CountDownLatch 构造函数中指定的count直接赋给AQS的state;每次countDown()则都是release(1)减1,最后减到0时unpark阻塞线程;这一步是由最后一个执行countdown方法的线程执行的。
  • 而调用await()方法时,当前线程就会判断state属性是否为0,如果为0,则继续往下执行,如果不为0,则使当前线程进入等待状态,直到某个线程将state属性置为0,其就会唤醒在await()方法中等待的线程。

CountDownLatch与Thread.join的区别

  • CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作
  • 而 join() 的实现原理是不停检查join线程是否存活,如果 join 线程存活则让当前线程永远等待