引言
CountDownLatch 就像游戏中的打BOOS,打BOOS之前有几道关卡,必须通过这几道关卡才能见到BOOS;
CountDownLatch 可以通过设置一个计数器,当计数器到0之前都会阻塞在 await 前面,通常是在多线程时,在子线程执行完后调用countDown进行倒数,待所有子线程执行完,计数器倒为0时再执行主线程 await 后面的逻辑;
测试程序
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
System.out.println(countDownLatch.getCount());
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + countDownLatch.getCount());
countDownLatch.countDown();
}, "t1").start();
System.out.println(Thread.currentThread().getName() + " " + countDownLatch.getCount());
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " " + countDownLatch.getCount());
}
}
await 阻塞
既然 await 会使线程阻塞,那它又是如何实现阻塞的呢?
通过跟踪源码 countDownLatch.await(); 最终会调到 AbstractQueuedSynchronizer 类的 doAcquireSharedInterruptibly 方法;然后看到 parkAndCheckInterrupt() 这个方法的调用
parkAndCheckInterrupt() 方法中通过 LockSupport.park(this) 阻塞当前线程,调用park后当前线程状态会从RUNNABLE 变为 WAITING;
countDown
既然 CountDownLatch.await() 会阻塞,那又是如何进行唤醒呢?
如图可看出 CountDownLatch 并没有提供直接唤醒的方法;
既然 CountDownLatch 维护的是一个计数器,那应该是计数器减为0时进行唤醒;
通过跟踪源码 countDown()方法,最终会调 tryReleaseShared() 方法对计数器减1,当计数器减到0时,开始唤醒阻塞的主线程,最后调 LockSupport.unpark(s.thread); 进行唤醒。
总结
CountDownLatch 内部维护了一个 Sync 内部类来实现计数,Sync 实现了 AbstractQueuedSynchronizer 类,ReentrantLock 也是通过内部使用AbstractQueuedSynchronizer 来实现的锁机制;AbstractQueuedSynchronizer 内部使用了LockSupport.park() 和 LockSupport.unpark() 来实现线程的阻塞和唤醒;
CountDownLatch 实现原理:
CountDownLatch → AbstractQueuedSynchronizer → LockSupport