【同步工具类:CountDownLatch】

介绍

Jdk原文翻译
CountDownLatch 一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
CountDownLatch用给定的计数初始化。由于countDown方法的调用,await方法会阻塞,直到当前计数达到零,然后释放所有等待线程,然后立即返回await的任何后续调用。这是一种一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用CyclicBarrier。
CountDownLatch是一种通用的同步工具,可用于多种目的。用计数1初始化的CountDownLatch用作一个简单的开/关锁存器或门:所有调用countDown的线程都在门处等待,直到它被调用countDown的线程打开。初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个操作,或某个操作完成N次。
CountDownLatch的一个有用的特性是,它不要求调用countDown的线程在继续之前等待计数达到零,它只是防止任何线程通过等待,直到所有线程都可以通过。
通俗理解
CountDownLatch 是一个多线程同步工具类,在多线程环境中它允许多个线程处于等待状态,直到前面的线程执行结束,从类名上看CountDown既是数量递减的意思,我们可以把它理解为计数器

源码分析

继承图

在这里插入图片描述

核心方法分析

await()

如下所示,await() 调用的是AQS的模板方法。然后 CountDownLatch.Sync 重新实现了 tryAccuqireShared方法。
从tryAccuqireShared 方法的实现来看,只要 state!=0,调用 await()方法的线程便会被放入AQS的阻塞队列,进入阻塞状态。

 public void await() throws InterruptedException {
 		//调用 AQS 的模板方法
        sync.acquireSharedInterruptibly(1);
    }
 
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
            //如果其他线程调用interrupt ,也可以唤醒 此线程
        if (Thread.interrupted())
            throw new InterruptedException();
            //CountDownLatch.Sync 重新实现了 tryAcquireShared 方法
        if (tryAcquireShared(arg) < 0)
        	//只要state!=0,就会放入阻塞队列进行阻塞,进入阻塞状态
            doAcquireSharedInterruptibly(arg);
    }

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

countDown()

countDown() 调用的AQS的模板方法 releaseShared(),里面的tryReleaseShared()被 CountDownLatch.Sync重新实现。
从下面的代码可以看出,只有state 的状态成功被改成了0。tryReleaseShared()方法才会返回true.然后执行doReleaseShared()。一次性唤醒队列中所有阻塞的线程。

 public void countDown() {
 		//调用AQS的模板方法
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) {
        //CountDownLatch.Sync 重新实现了 tryReleaseShared 方法
        if (tryReleaseShared(arg)) {
        	//唤醒队列中所有阻塞的线程
            doReleaseShared();
            return true;
        }
        return false;
    }
 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;
                //只有 c==1, 减一后变成0.然后将 c 的值改成0.此时才返回true.
                //然后唤醒阻塞队列中的所有阻塞线程
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

业务场景

有个人脸识别应用,比如我们常见的上班打卡应用。应用首次启动要求多线程将存储在DB中的人脸数据加载到本地应用中,主线程需要等待所有子线程完成任务后,才能继续执行余下的业务逻辑,比如加载 dubbo组件啥的。

代码实现

此处使用了两个CountDownLatch ,一个是用于准备DB读取的准备工作,另一个是等待DB读取工作完成后开始加载其他组件。

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(10);
        for (int i = 0; i < 10; ++i) // create and start threads
             new Thread(new DoDemo(startSignal, doneSignal)).start();
        doSomethingElse();
        //开始加载数据,
        startSignal.countDown();
        //主线程阻塞,等待数据加载完成
         doneSignal.await();
        doSomethingElse();
        System.out.println("数据加载完成,开始启动其他组件,包括dubbo组件");
    }

    public static void doSomethingElse(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class DoDemo extends Thread {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;

    public DoDemo(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }

    @Override
    public void run() {
        try {
            //开始阻塞了,等待主线程开启
            System.out.println(Thread.currentThread().getName()+" 开始阻塞等待了...");
            startSignal.await();
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        }
    }

    public void doWork() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " 开始干活了,DB 中的数据加载到本地缓存中");
        Thread.sleep(1000);
    }
}

测试结果

在这里插入图片描述

总结

多线程并发的情况下需要做好同步处理,结合CountDownLatch 充分的运用到业务场景当中还是挺有必要的,凡是需要在多个任务执行完成后再去做另一件事的情况都可以考虑使用CountDownLatch。合理使用但请不要滥用,特别上面也提到过计数值需要确定,否则可能导致多任务无法做到同步甚至造成主线程无限等待。
注:
Semaphore 和CountDownLatch 的实现方式比较类似。大家可以比较学习。
Semaphore 学习可以参考:

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值