AQS源码解读(八)——CountDownLatch倒数器原理详解

    科技2025-02-14  37

    更多JUC源码解读系列文章请持续关注JUC源码解读文章目录JDK8!


    文章目录

    前言代码示例CountDownLatch基本结构CountDownLatch.Sync#tryAcquireSharedCountDownLatch.Sync#tryReleaseShared

    前言

    CountDownLatch是基于AQS实现的共享锁,它的主要用途是主线程需要等待子线程执行完任务才能继续往下执行,可以叫它倒数器。

    CountDownLatch countDownLatch = new CountDownLatch(10); countDownLatch.await();//主线程调用await()被阻塞(获取锁失败) countDownLatch.countDown(); //10个worker线程执行完任务countDown(),当state减到0,主线程被唤醒

    代码示例

    模拟一个线程调用countDownLatch.await(),另外10个线程调用countDownLatch.countDown()。

    public class Test1071 { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(10); new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " invoke countDownLatch.await()"); countDownLatch.await(); System.out.println(Thread.currentThread().getName() + ",被唤醒。。"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println("countDown.."); countDownLatch.countDown(); } }).start(); } } } //控制台输出 Thread-0 invoke countDownLatch.await() countDown.. countDown.. countDown.. countDown.. countDown.. countDown.. countDown.. countDown.. countDown.. countDown.. Thread-0,被唤醒。。

    CountDownLatch基本结构

    CountDownLatch代码结构要比ReentrantLock和ReentrantReadWriteLock简单很多,功能也简单,就是基于AQS共享锁实现一个倒数器的功能。

    public class CountDownLatch { /** * Synchronization control For CountDownLatch. * Uses AQS state to represent count. */ 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) { return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } } private final Sync sync; public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public void countDown() { sync.releaseShared(1); } public long getCount() { return sync.getCount(); } public String toString() { return super.toString() + "[Count = " + sync.getCount() + "]"; } }

    CountDownLatch.Sync没有子类,无需区分公平锁和非公平锁。await()调用了AQS中的模板方法acquireSharedInterruptibly,countDown()调用了AQS中的releaseShared,所以只需要看看tryAcquireShared和tryReleaseShared在CountDownLatch中的具体实现。

    CountDownLatch.Sync#tryAcquireShared

    线程调用CountDownLatch#await(),进而调用了AbstractQueuedSynchronizer#acquireSharedInterruptibly可中断获取共享锁。从CountDownLatch.Sync#tryAcquireShared源码看出,只有state=0的时候,才能获取锁,否则进入队列阻塞。

    线程调用CountDownLatch#await(long, java.util.concurrent.TimeUnit),进而调用了AbstractQueuedSynchronizer#tryAcquireSharedNanos可超时中断获取共享锁。如果线程调用了CountDownLatch#await(long, java.util.concurrent.TimeUnit),当时间到了,state还没有减到0,也会直接返回false,从而往下执行,不再阻塞。

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

    CountDownLatch.Sync#tryReleaseShared

    线程调用CountDownLatch#countDown,释放共享锁(AbstractQueuedSynchronizer#releaseShared),当所有的共享锁释放,则唤醒同步队列中调用CountDownLatch#await()的线程。

    tryReleaseShared中CAS自旋对state做减法,当state减到0时返回true,方可继续调用doReleaseShared唤醒后继。

    protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }

    总结:

    CountDownLatch的原理很简单,调用await()和countDown()属于两种线程,简称为await线程和countDown线程,await线程调用await()相当于获取锁失败,所以会进入同步队列阻塞,countDown线程调用countDown()相当于释放锁,只有全部释放了state=0,才会唤醒await线程。new CountDownLatch(10)初始化给一定数值,CountDownLatch#countDown相当于倒数,倒数到0,唤醒await线程。若有多个await线程被阻塞,因为是共享锁,所以一个wait线程被唤醒获取锁后会接连唤醒其他阻塞的wait线程。

    PS: 如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!

    Processed: 0.015, SQL: 8