CountDownLatch源码分析

CountDownLatch源码分析

CountDowntLatch的作用是让主线程等待所有的子线程执行完毕之后再进行执行,同时它是基于AQS进行实现的,所以它底层肯定是通过自定义AQS共享模式下的同步器来实现的,该同步器需要重写AQS提供的tryAcquireShared()以及tryReleaseShared()方法,只需要告诉AQS是否尝试获取同步资源以及释放同步资源成功即可。

AQS子类需要定义以及维护同步状态的值,在CountDownLatch中,同步状态state的值为同步资源的个数。


CountDownLatch的结构

public class CountDownLatch {
    
    /**
     * 存在一个AQS共享模式下的同步器
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
       // ......
    }

    // 存在一个全局的同步器属性
    private final Sync sync;
    
    /**
     * 构建方法初始化同步器,并指定同步资源的个数
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    
    /**
     * 让主线程进行阻塞
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    
    /**
     * 让倒数器-1
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

可以看到CountDownLatch中定义了一个同步器,然后存在一个全局的同步器属性,并且通过构建方法来初始化同步器,通过count参数来指定同步资源的个数。

同时CountDownLatch的await()方法将会直接调用AQS的acquireSharedInterruptibly()方法,countDown()方法直接调用AQS的releaseShared()方法。


剖析同步器

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    /**
     * 构建方法初始化同步资源的个数
     */
    Sync(int count) {
        setState(count);
    }

    /**
     * 获取可用的同步资源个数(就是倒数器当前的值)
     */
    int getCount() {
        return getState();
    }

    /**
     * 尝试获取同步资源
     */
    protected int tryAcquireShared(int acquires) {
        // 只有当同步状态的值为0,方法才返回true
        return (getState() == 0) ? 1 : -1;
    }

    /**
     * 尝试释放同步资源
     */
    protected boolean tryReleaseShared(int releases) {
        // 死循环保证CAS操作成功
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            // 让同步状态的值-1
            int nextc = c-1;
            // 只有当线程释放同步资源后,同步状态的值0时,该方法才会返回true
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

tryAcquireShared()方法用于尝试获取同步资源,同时AQS规定,如果获取同步资源成功则返回剩余的可用资源个数,否则返回负数,但在CountDownLatch的同步器的tryAcquireShared()方法中,只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,否则都为获取同步资源失败,方法返回-1。

tryReleaseShared()方法用于尝试释放同步资源,同时AQS规定,如果释放同步资源成功,则返回true,否则返回false,一般情况下方法都会返回true(当前同步资源的值 + 要释放的同步资源个数),但是在CountDownLatch的同步器的tryReleaseShared()方法中,并没有累加同步状态的值,而是当线程每次释放同时资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true。


流程总结

1.首先创建一个CountDownLatch实例,并指定倒数器的阈值。

2.主线程调用CountDownLatch的await()方法进行阻塞,该方法会直接调用AQS的acquireSharedInterruptibly()方法,acquireSharedInterruptibly()方法又会调用同步器的tryAcquireShared()方法,尝试获取同步资源,tryAcquireShared()方法只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,由于目前同步状态的值不为0,因此方法返回-1,因此该线程将会封装成Node节点,然后加入到等待队列当中,该线程将会进行阻塞。

3.子线程调用CountDownLatch的countDown()方法让倒数器-1,该方法会直接调用AQS的releaseShared()方法,releaseShared()方法又会调用同步器的tryReleaseShared()方法,尝试释放同步资源,但是在tryReleaseShared()方法中,每次当线程释放同步资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true,否则返回false,如果tryReleaseShared()方法返回false,那么就不做任何处理,只有当该方法返回true时,也就是最后一个子线程执行了countDown()方法,将同步状态的值设置为0,那么就会唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,也就是主线程,然后主线程调用tryAcquireShared()方法尝试获取同步资源,由于当前同步状态的值已经为0,因此tryAcquireShared()方法返回1,然后主线程直接返回,做自己的事情。


FAQ

CountDownLatch为什么不能重用?

不能重用,因此当主线程被唤醒后,然后调用tryAcquireShared()方法获取了同步资源,然后就直接返回,做自己的事情,永远都不会释放同步资源,因此不能重用。

posted @ 2020-09-07 14:32  辣鸡小篮子  阅读(387)  评论(0编辑  收藏  举报