同样是调用同步器提供的模版方法 releaseShared
public final boolean releaseShared(int arg) { // 调用自己重写的同步器方法 if (tryReleaseShared(arg)) { // 唤醒调用 await() 被阻塞的线程 doReleaseShared(); return true; } return false; }重写的 tryReleaseShared 同样很简单
protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); // 如果当前状态值为0,则直接返回 (1) if (c == 0) return false; // 使用 CAS 让计数器的值减1 (2) int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }代码 (1) 判断当前同步状态值,如果为0 则直接返回 false;否则执行代码 (2),使用 CAS 将计数器减1,如果 CAS 失败,则循环重试,最终返回 nextc == 0 的结果值,如果该值返回 true,说明最后一个线程已调用 countDown() 方法,然后就要唤醒调用 await() 方法被阻塞的线程,同样由于分析过 AQS 的模版方法 doReleaseShared 整个释放同步状态以及唤醒的过程,所以这里同样不再赘述了
仔细看CountDownLatch重写的 tryReleaseShared 方法,有一点需要和大家说明:
代码 (1) if (c == 0) 看似没什么用处,其实用处大大滴,如果没有这个判断,当计数器值已经为零了,其他线程再调用 countDown 方法会将计数器值变为负值
现在就差 await(long timeout, TimeUnit unit) 方法没介绍了
await(long timeout, TimeUnit unit) public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }该方法签名同样抛出 InterruptedException,意思可响应中断。它其实就是 await() 更完善的一个版本,简单来说就是
主线程设定等待超时时间,如果该时间内子线程没有执行完毕,主线程也会直接返回
我们将上面的例子稍稍修改一下你就会明白(主线程超时时间设置为 2 秒,而子线程要 sleep 5 秒)
@Slf4j public class CountDownLatchTimeoutExample { private static CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { // 这里不推荐这样创建线程池,最好通过 ThreadPoolExecutor 手动创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-1 执行完毕"); //计数器减1 countDownLatch.countDown(); } }); executorService.submit(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-2 执行完毕"); //计数器减1 countDownLatch.countDown(); } }); log.info("主线程等待子线程执行完毕"); log.info("计数器值为:" + countDownLatch.getCount()); countDownLatch.await(2, TimeUnit.SECONDS); log.info("计数器值为:" + countDownLatch.getCount()); log.info("主线程执行完毕"); executorService.shutdown(); } }运行结果如下:
形象化的展示上述示例的运行过程
小结CountDownLatch 的实现原理就是这么简单,了解了整个实现过程后,你也许发现了使用 CountDownLatch 的一个问题:
计数器减 1 操作是一次性的,也就是说当计数器减到 0, 再有线程调用 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用了
为了解决这个问题,贴心的 Doug Lea 大师早已给我们准备好相应策略 CyclicBarrier