用一句话说明白CountDownLatch倒数器的原理

本文解析了CountDownLatch的基本原理,如何通过模拟线程调用await()等待和countDown()解锁来实现倒数计数,以及其AQS共享锁实现。重点讲解了tryAcquireShared和tryReleaseShared方法的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


前言

假设CountDownLatch从10开始倒数,就相当于一个大门有10把锁,只有10把锁都打开了,才能进门,否则都会被堵在门口。而调用countDown()就是在解锁,在没有解开所有锁之前调用await()就会阻塞。

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代码结构要比ReentrantLockReentrantReadWriteLock简单很多,功能也简单,就是基于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中的模板方法acquireSharedInterruptiblycountDown()调用了AQS中的releaseShared,所以只需要看看tryAcquireSharedtryReleaseSharedCountDownLatch中的具体实现。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐同学呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值