【源码】JUC —— CountDownLatch 浅析

前言

CountDownLatch ,主要用于如下场景:待线程阻塞到一定数量后,统一唤醒。有点跟 Semaphore 反着来的意思

先养剧,再看剧

JDK 版本

JDK11

Sync

基于 AQS 实现,内部类 Sync 继承 AQS 实现相关方法

		Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

Semaphore 的构造类似,但此处的 count 表示“倒计时”阈值

关于 Semaphore ,可阅读

【源码】JUC —— Semaphore 浅析

tryAcquireShared

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

state == 0 时,才表明获取到资源

tryReleaseShared

		protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                // 若 state 为 0,即被阻塞线程已经被唤醒了
                if (c == 0)
                    return false;
                
                // 否则,CAS 修改 state 递减
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

此处相当于是 倒计时,倒计时到 0 时,所有阻塞的线程被唤醒,同时起跑

得益于 AQS 同步资源获取的实现。不同于 独占锁, AQS 在获取同步资
源后,也会尝试唤醒符合条件的排队节点。因此可以保证,排队节点几乎
同时被唤醒,达到“倒计时”结束同时“起跑”的效果

构造

	public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

初始化“倒计时”阈值

方法实现(委托)

	/**
     * 获取 共享资源,响应中断
     * 此处的 共享资源 实际是设置的 倒计时
     * 因此会在 大于0 是进入队列排队
     * 倒计时结束后被唤醒
     */
	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();
    }

反向实现 共享锁

demo

public class CountDownLatchTest {

    static CountDownLatch countDownLatch = new CountDownLatch(4);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    test();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t" + i).start();

            TimeUnit.SECONDS.sleep(1);

            countDownLatch.countDown();
        }

        countDownLatch.countDown();
    }

    static void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " start");
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + " done");
    }
}

结果:
--------- 陆续 ------
t0 start
t1 start
t2 start

--------- 同时 -------
t0 done
t2 done
t1 done

三个线程 t0 t1 t2 依次进入并等待,待“倒计时”结束后,同时被唤醒

总结

对于某些需要等待目标线程完成操作后才继续之后动作的场景,可以使用 CountDownLatch,比如 SpringBoot 提供的 BackgroundPreinitializer 事件监听器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值