基于核心源码和个人思考——CountDownLatch

CountDownLatch

  • CountDownLatch通过内部类Sync继承AbstractQueuedSynchronizer类并使用它的共享资源机制实现的。
  • 初始化时给定计数值,并分派给线程,线程执行完后数值减1并阻塞,当计数值为0后,所有线程继续执行。

内部类Sync

private static final class Sync extends AbstractQueuedSynchronizer

  • 继承了同步队列AQS
  • CountDownLatch的核心逻辑由Sync内部类实现

Sync方法

  • int getCount()
  • int tryAcquireShared(int acquires) 尝试获取共享资源,这里用于阻塞线程,直到所有线程到齐。
    • CountDownLatch通过state计数。当线程await()时,会在这里尝试获取资源,只有state变为0,才能获取成功。
    • 线程阻塞后被唤醒,也会再次调用这个方法,再次尝试获取锁资源。
// 本方法重写了AQS类的方法。
// 返回值:大于等于0,获取共享资源成功;小于零,获取失败。
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
复制代码
  • boolean tryReleaseShared(int releases) 释放共享资源,这里用于线程到齐后全部释放。
    • 每当线程调用await方法,则state减1,若减到0,则线程不再阻塞。
    • 这里若返回true,AQS会去唤醒队列中的阻塞线程重新竞争资源。由于tryAcquireShared中,state等于0既能获取到资源,则所有阻塞线程都会被释放。
// 本方法重写了AQS类的方法。
protected boolean tryReleaseShared(int releases) {
    for (;;) { // 自旋
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc)) // CAS替换state状态
            return nextc == 0;
    }
}
复制代码

属性

  • Sync sync 实现核心阻塞释放逻辑的内部类

方法

  • void await() 阻塞致计数值为0
  • boolean await(long timeout, TimeUnit unit)
  • void countDown() 计数值减1
  • long getCount() 获取计数值

相关问题

1. 为什么CountDownLatch不能重置,能否实现重置功能?

  • 这里其实是相对于CyclicBarrier类来说的,CyclicBarrier在最后一个线程到达后会自动释放所有线程,并自动重置,当然也可以手动重置。
  • 关于自动重置,CountDownLatch是通过两个方法来控制阻塞,一个是统计到达的线程数量countDown,一个是阻塞释放线程await,因为await只知道线程到齐与否,并不知道自己是不是最后一个到齐的,所以await不能自动重置。而countDown虽然知道最后一个线程到达的时间,但是因最后一个线程调用countDown的时候,还没调用await,如果调用countDown就自动重置了,再调用await时就会被阻塞,不符合逻辑要求。
  • 关于手动重置,我觉得是可以的。加一个属性parties存储拦截的数量,增加一个reset方法,通过调用AQS的setState方法把state重新设为parties值即可重新计数。但是要对重置时,已拦截的线程是做释放处理还是抛异常或者是继续拦截进行进一步的管理。

2. CountDownLatch通过AQS的state属性计数,state是共享独占机制共用的属性,为什么要用共享锁机制来实现呢?

  • 独占机制只允许一个线程获取到资源,如果使用独占机制的话,要持有资源线程先释放资源才能唤醒后续线程。独占机制做法:A获取资源->A释放资源->唤醒B线程->B获取资源->...->所有线程唤醒完毕
  • 共享机制允许多个线程同时持有资源,因此使用共享机制可以在某个线程释放资源之前允许下一个线程尝试获取资源,共享机制的做法的做法:A获取资源->唤醒B线程->B获取资源->唤醒C线程->...->所有线程唤醒完毕。

与CyclicBarrier比较

  • CountDownLatch不可重置,CyclicBarrier可重置。
  • CountDownLatch使用时计数阻塞是两个方法,比较麻烦;CyclicBarrier只有await,实现了计数阻塞甚至重置为一体,更简洁。
  • CountDownLatch在实现 线程在某个点需要等待另一个线程先执行完 的这种条件似乎会更有优势,CyclicBarrier则是让多个任务并行执行,最后统一汇总的情况比较方便。

猜你喜欢

转载自juejin.im/post/5e4e33ece51d45271b7468c9