详解CountDownLatch

158 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

CountDownLatchJDK提供的同步工具,可以实现让一个线程等待其它一组线程执行完毕,然后再继续往下执行的功能。本篇文章将对CountDownLatch的内部原理进行分析。

正文

CountDownLatch内部有一个静态内部类Sync继承了AbstractQueuedSynchronizer,在CountDownLatch初始化时就会创建Sync对象,后续的功能也是依靠Sync对象实现。下面先看一下CountDownLatchUML图。

CountDownLatch类图

CountDownLatch在初始化时会先校验传入的count值,如果小于0则抛出异常,然后再创建Sync对象。Sync的构造函数如下所示。

Sync(int count) {
    setState(count);
}

setState() 方法是AbstractQueuedSynchronizer提供的方法,其作用是设置AbstractQueuedSynchronizerstate字段的值,所以创建CountDownLatch对象后,与这个CountDownLatch对象关联的AbstractQueuedSynchronizerstate值会被设置为创建CountDownLatch对象时指定的count值。

下面再分析CountDownLatchawait() 方法,如下所示。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

CountDownLatchawait() 方法会调用到AbstractQueuedSynchronizeracquireSharedInterruptibly() 方法,其实现如下。

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() 方法将当前线程入同步队列并进入共享式阻塞状态。那么CountDownLatchawait() 方法能够共享式阻塞调用该方法的线程,关键就在于Sync实现的tryAcquireShared() 方法,其实现如下。

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

Sync实现的tryAcquireShared() 方法中就是对state值进行判断,只要非0就会返回-1,从而让调用CountDownLatchawait() 方法的线程入同步队列并共享式阻塞,而已知CountDownLatch在初始化时会设置state的值,所以只要设置的state的值大于0,并且没有其它线程调用CountDownLatchcountDown() 方法,那么CountDownLatchawait() 方法就会共享式阻塞调用线程。

有了阻塞,就会有唤醒,下面看一下CountDownLatchcountDown() 方法。

public void countDown() {
    sync.releaseShared(1);
}

CountDownLatchcountDown() 方法中会调用AbstractQueuedSynchronizerreleaseShared() 方法,其实现如下所示。

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;
    }
}

SynctryReleaseShared() 方法在state减1后等于0时会返回true,也就是说在唤醒线程时,state的值为0。现在继续分析唤醒线程的AbstractQueuedSynchronizer#doReleaseShared方法,该方法会共享式的释放同步状态,然后唤醒在同步队列上共享式阻塞的线程,唤醒后的线程会先调用AbstractQueuedSynchronizer的子类实现的tryAcquireShared() 方法来共享式获取锁,如果该方法返回1,则表明共享式获取锁成功并且锁还可以继续被获取,此时唤醒的操作会传递下去。先看一下SynctryAcquireShared() 方法的实现。

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

上面提到,唤醒线程时,state的值为0,所以此时调用tryAcquireShared() 方法会一直返回1,则唤醒同步队列上共享式阻塞的线程的这个操作会依次传递下去,最终所有因为调用CountDownLatchcountDown() 方法而共享式阻塞在同步队列上的线程都会被唤醒。

总结

  1. CountDownLatch初始化时传入的count值必须大于等于0,当count等于0时,可以完成初始化,但是后续调用CountDownLatchawait() 方法时,不会阻塞调用线程;
  2. 通过调用CountDownLatchawait() 方法而被阻塞的线程,是共享式阻塞状态;
  3. 通过调用CountDownLatchcountDown() 方法而使count值等于0时,此时会共享式的依次唤醒所有因为调用CountDownLatchawait() 方法而共享式阻塞的线程。

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情