开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
CountDownLatch是JDK提供的同步工具,可以实现让一个线程等待其它一组线程执行完毕,然后再继续往下执行的功能。本篇文章将对CountDownLatch的内部原理进行分析。
正文
CountDownLatch内部有一个静态内部类Sync继承了AbstractQueuedSynchronizer,在CountDownLatch初始化时就会创建Sync对象,后续的功能也是依靠Sync对象实现。下面先看一下CountDownLatch的UML图。

CountDownLatch在初始化时会先校验传入的count值,如果小于0则抛出异常,然后再创建Sync对象。Sync的构造函数如下所示。
Sync(int count) {
setState(count);
}
setState() 方法是AbstractQueuedSynchronizer提供的方法,其作用是设置AbstractQueuedSynchronizer的state字段的值,所以创建CountDownLatch对象后,与这个CountDownLatch对象关联的AbstractQueuedSynchronizer的state值会被设置为创建CountDownLatch对象时指定的count值。
下面再分析CountDownLatch的await() 方法,如下所示。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
CountDownLatch的await() 方法会调用到AbstractQueuedSynchronizer的acquireSharedInterruptibly() 方法,其实现如下。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
AbstractQueuedSynchronizer#acquireSharedInterruptibly方法会先调用其子类Sync实现的tryAcquireShared() 方法,如果tryAcquireShared() 方法返回值小于0,则调用AbstractQueuedSynchronizer#doAcquireSharedInterruptibly() 方法将当前线程入同步队列并进入共享式阻塞状态。那么CountDownLatch的await() 方法能够共享式阻塞调用该方法的线程,关键就在于Sync实现的tryAcquireShared() 方法,其实现如下。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
在Sync实现的tryAcquireShared() 方法中就是对state值进行判断,只要非0就会返回-1,从而让调用CountDownLatch的await() 方法的线程入同步队列并共享式阻塞,而已知CountDownLatch在初始化时会设置state的值,所以只要设置的state的值大于0,并且没有其它线程调用CountDownLatch的countDown() 方法,那么CountDownLatch的await() 方法就会共享式阻塞调用线程。
有了阻塞,就会有唤醒,下面看一下CountDownLatch的countDown() 方法。
public void countDown() {
sync.releaseShared(1);
}
在CountDownLatch的countDown() 方法中会调用AbstractQueuedSynchronizer的releaseShared() 方法,其实现如下所示。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
AbstractQueuedSynchronizer#releaseShared方法中会先调用AbstractQueuedSynchronizer的子类Sync实现的tryReleaseShared() 方法,该方法如果返回true,就会调用AbstractQueuedSynchronizer#doReleaseShared方法来共享式唤醒线程,下面先分析一下Sync实现的tryReleaseShared() 方法,如下所示。
protected boolean tryReleaseShared(int releases) {
for (; ; ) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
Sync的tryReleaseShared() 方法在state减1后等于0时会返回true,也就是说在唤醒线程时,state的值为0。现在继续分析唤醒线程的AbstractQueuedSynchronizer#doReleaseShared方法,该方法会共享式的释放同步状态,然后唤醒在同步队列上共享式阻塞的线程,唤醒后的线程会先调用AbstractQueuedSynchronizer的子类实现的tryAcquireShared() 方法来共享式获取锁,如果该方法返回1,则表明共享式获取锁成功并且锁还可以继续被获取,此时唤醒的操作会传递下去。先看一下Sync的tryAcquireShared() 方法的实现。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
上面提到,唤醒线程时,state的值为0,所以此时调用tryAcquireShared() 方法会一直返回1,则唤醒同步队列上共享式阻塞的线程的这个操作会依次传递下去,最终所有因为调用CountDownLatch的countDown() 方法而共享式阻塞在同步队列上的线程都会被唤醒。
总结
- CountDownLatch初始化时传入的count值必须大于等于0,当count等于0时,可以完成初始化,但是后续调用CountDownLatch的await() 方法时,不会阻塞调用线程;
- 通过调用CountDownLatch的await() 方法而被阻塞的线程,是共享式阻塞状态;
- 通过调用CountDownLatch的countDown() 方法而使count值等于0时,此时会共享式的依次唤醒所有因为调用CountDownLatch的await() 方法而共享式阻塞的线程。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情