理解Java并发工具类CountDownLatch

CountDownLatch相信大家并不陌生,我们在上篇文章中已经分析其实现,这里在简单回顾一下CountDownLatch是基于AQS共享锁构建的一种同步器,它的主要应用场景有两种:

(1)一个线程等待所有的其他线程执行完任务之后自己再执行自己的任务。

(2)多个线程等待一个线程执行完任务之后,然后多个线程同时开始执行自己的任务。

在实际开发中,可能大家仅仅对第一种场景比较熟悉,而完全忽视了第二种场景,实际上第二种场景才是CountDownLatch发挥共享锁的真正案例。

CountDownLatch的方法主要是:

(1)构造方法:

CountDownLatch(int count)
参数count控制线程的数量

(2)await()
阻塞当前调用的线程,直到count的值等于0才唤醒,除非执行了线程中断,否则
在没到达0之前,一直处于waiting状态

(3)await(long timeout, TimeUnit unit)

阻塞当前调用的线程,直到count的值等于0才唤醒,除非执行了线程中断或者指定的时间周期过期,否则在没到达0之前,一直处于waiting状态

(4)countDown()
每次调用对count的值减1,当这个值到达0的时候,会释放所有等待的线程。


(5)getCount()
 返回当前count的数量

下面我们看一个比较典型的一个例子:

package concurrent.tools;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
 * Created by Administrator on 2018/8/20.
 */
public class CountDownDemo2 {





    static class Worker implements Runnable{

        private final CountDownLatch startSignal;
        private final CountDownLatch dongSignal;

        Worker(CountDownLatch startSignal,CountDownLatch dongSignal){
            this.startSignal=startSignal;
            this.dongSignal=dongSignal;
        }
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"  启动了,等待main线程调度.......");
                startSignal.await();
                doWork();
                System.out.println(Thread.currentThread().getName()+"  完活 ..... ");
                dongSignal.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


        void doWork() throws InterruptedException {

            System.out.println(Thread.currentThread().getName()+"  开始工作 ..... ");
            Thread.sleep(5000);

        }
    }



    public static void main(String[] args) throws InterruptedException {


        CountDownLatch startSignal=new CountDownLatch(1);//
        CountDownLatch doneSignal=new CountDownLatch(5);

        for(int i=0;i<5;i++){

            new Thread(new Worker(startSignal,doneSignal)).start();
        }
        Thread.sleep(4000);
        System.out.println(Thread.currentThread().getName()+"线程准备就绪,所有线程可以开始工作了..... ");
        startSignal.countDown();
        doneSignal.await();

        System.out.println(Thread.currentThread().getName()+"线程监控任务结束 ");






    }
}

执行完成之后,输出结果如下:

Thread-0  启动了,等待main线程调度.......
Thread-2  启动了,等待main线程调度.......
Thread-1  启动了,等待main线程调度.......
Thread-3  启动了,等待main线程调度.......
Thread-4  启动了,等待main线程调度.......
main线程准备就绪,所有线程可以开始工作了..... 
Thread-0  开始工作 ..... 
Thread-2  开始工作 ..... 
Thread-4  开始工作 ..... 
Thread-3  开始工作 ..... 
Thread-1  开始工作 ..... 
Thread-2  完活 ..... 
Thread-3  完活 ..... 
Thread-4  完活 ..... 
Thread-1  完活 ..... 
Thread-0  完活 ..... 
main线程监控任务结束

上面的例子就是一个非常典型的例子,反应到实际生活的场景比如,张三生日party,准备在一个大酒店进行,客房里面有4个服务员等待服务上菜,前提是张三所有的朋友必须到齐才能开始宴会,然后张三就是协调者,在所有朋友到齐之后,张三发话开始上菜,这时候4个服务员就可以去同时进行上菜。这个例子里面就和上面我们代码执行的例子非常类似。此外还有在web服务器中,必须等缓存初始化之后,我们的程序才对外提供服务,那么这个场景也可以使用CountDownLatch来完成。

这里大家需要避免一个误区,大多数时候我们都是多个线程调用 countDown,只有一个线程调用await, 但实际情况是await方法也是可以有多个线程调用的,而这正是共享锁的体现。

关于CountDownLatch使用的几个步骤:

(1)构造函数指定需要等待的线程数量

(2)对于执行countDown方法的线程为了安全起见这个调用必须写在finally块里面,防止线程发生异常退出,导致程序永远不会终止。

(3)对于异常终止判断,我们可以通过一个布尔变量或者CountDownLatch的getCount方法来判断是不是有的任务异常退出,从而决定需要做什么

@Override protected void onKernalStart0() 
                    throws IgniteCheckedException {
    try {
        queueHdrView = cctx.cache();
        initFlag = true;
    }
    finally {
        initLatch.countDown();
    }
}

(4)对于执行await方法的线程,我们需要判断是否有效,如果无效则要抛出终端异常。

public static void await(CountDownLatch latch) 
              throws IgniteInterruptedCheckedException {
    try {
        if (latch.getCount() > 0)
            latch.await();
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new IgniteInterruptedCheckedException(e);
    }
}

最后需要注意的是CountDownLatch仅仅只能被用一次,不能被重置,如果需要循环重置则需要使用Java并发工具包的另外一个类CyclicBarrier。这个会在下一篇文章中介绍。

本文分享自微信公众号 - 我是攻城师(woshigcs)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

为什么用 Java:一个 Python 程序员告诉你

说实话,本人刚开始的时候也是同样的反应。但是由于Java的类型安全,执行性能和坚如磐石的工具,我渐渐地开始欣赏Java。同时我注意到,现在的Java已今非昔比—...

11010
来自专栏Grace development

PHP程序员如何简单的开展服务治理架构(二)

服务治理 治理的绝笔是服务,在一家公司有玩各种语言的程序员,如何去统一管理他们开发的服务,这是一个问题。

20020
来自专栏java一日一条

关于Spring的69个面试问答——终极列表

这篇文章总结了一些关于Spring框架的重要问题,这些问题都是你在面试或笔试过程中可能会被问到的。下次你再也不用担心你的面试了,Java Code Geeks这...

8310
来自专栏java一日一条

GitHub上那些值得一试的Java开源库

作为一名程序员,你几乎每天都会使用到GitHub上的那些著名Java第三方库,比如Apache Commons,Spring,Hibernate等等。除了这些,...

34620
来自专栏java一日一条

用Java实现一个通用并发对象池

这篇文章里我们主要讨论下如何在Java里实现一个对象池。最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来...

14720
来自专栏java一日一条

为Web开发者准备的10个最新工具

Web开发设计是一个很有前途的职业。然而,这其中也有许多挑战。现在的企业和品牌正在朝网络进军。这给了web开发者非常多的机会来展示他们的技能,并在他们的职业上取...

15830
来自专栏java一日一条

13个不容错过的Java项目

GitHub可谓一座程序开发的大宝库,有些素材值得fork,有些则能帮助我们改进自有代码或者学习编程技能。无论如何,开发工作当中我们几乎不可能绕得开GitHub...

99510
来自专栏Java后端技术栈

怎么样才算一个靠谱的程序员!

我的上一份工作是在一家世界500强金融集团担任架构师,当时,公司的IT团队规模将近2000人。与其他IT公司一样,程序员的流动性也比较高,而作为架构师,我需要为...

8210
来自专栏java一日一条

Java 消亡了?不!原因在这…

年复一年,关于”Java消亡了?”的疑问频繁涌现,然而,通过所有外部表现来看,Java仍活着,并且在发展。尽管许多新语言各领风骚,开发语言排行榜(TIOBE)上...

9320
来自专栏java一日一条

Java 并发包中的读写锁及其实现分析

在Java并发包中常用的锁(如:ReentrantLock),基本上都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时 刻可以允许多个读线程访...

8920

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励