并发编程 — CountDownLatch 详解

一、概述

类 CountDownLatch 是一个同步功能的辅助类,使用效果是给定一个计数,当使用这个CountDownLatch类的线程判断计数不为0时,则呈wait状态,如果为0时则继续运行。实现等待与继续运行的效果分别需要使用await()和countDown()方法来进行。调用await()方法时判断计数是否为0,如果不为0则呈等待状态。其他线程可以调用countDown()方法将计数减1,当计数减到为0时,呈等待的线程继续运行。而方法getCount()就是获得当前的计数个数。

二、使用场景

1、某一线程在开始运行前等待n个线程执行完毕

比如实现一个机票比价场景,需要调用各个航空公司的机票价格,然后进行价格排序,就可以通过 CountDownLatch 来实现,代码如下所示:


public class CountDownLatchExample1 {

    public static void main(String[] args) {
        List<String> airs = Arrays.asList("东方航空", "南方航空", "成都航空", "北京航空");

        //定义一个CountDownLatch对象,指定数量为航空公司的个数
        CountDownLatch latch = new CountDownLatch(airs.size());

        List<Pair<String, Integer>> list = new ArrayList<>();

        //定义四个线程模拟调用 航空公司的外部接口
        Thread[] threads = new Thread[airs.size()];
        for(int i = 0; i < airs.size(); i++){
            threads[i] = new Thread(() -> {
                try {
                    // 模拟调用耗时
                    TimeUnit.SECONDS.sleep(current().nextInt(10));
                    //构建价格
                    Pair<String, Integer> pair = new Pair<>(Thread.currentThread().getName(), current().nextInt(100));
                    list.add(pair);
                    System.out.println(pair.toString());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    latch.countDown(); //执行完后 是计数器减1
                }
            }, airs.get(i));
        }
        //启动所有的线程
        Stream.of(threads).forEach(Thread::start);
        System.out.println("等待所有的线程执行完");
        try {
            latch.await(); // 在此等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        list.sort((o1, o2) -> {
            if(o1.getValue() > o2.getValue()){
               return 1;
            }else if (o1.getValue().equals(o2.getValue())){
                return 0;
            }else {
                return -1;
            }
        });

        System.out.println("========最终结果=======");
        list.forEach(System.out::println);
    }


}

 执行结果:

2、实现多个线程开始执行任务的最大并行性


public class CountDownLatchExample2 {
    //裁判类
    static class Referee{
        private CountDownLatch downLatch = new CountDownLatch(1);
        private CountDownLatch latch;

         Referee(int count){
            latch = new CountDownLatch(count);
        }

        // 各就位
         void prepare()  {
            try {
                System.out.printf("线程[%s]准备就绪 \n", Thread.currentThread().getName());
                latch.countDown(); //运动员就位
                downLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.printf("线程[%s]结束 \n", Thread.currentThread().getName());
        }
        //开始执行
        public void start(){
            try {
                latch.await(); //等待运动员就位
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("开始");
            downLatch.countDown();
        }
    }

    // 运动员类
    static class SportsMan extends  Thread{
        private Referee referee;

        SportsMan(Referee referee) {
            this.referee = referee;
        }

        @Override
        public void run() {
            referee.prepare(); // 运动员准备
        }
    }


    public static void main(String[] args) {
        //定义裁判指定10个运动员
        Referee referee = new Referee(10);
        //定义 10 个运动员
        SportsMan[] mans = new SportsMan[10];
        for (int i = 0; i < 10; i++){
            mans[i] = new SportsMan(referee);
            mans[i].setName("Thread-" + i);
        }
        Stream.of(mans).forEach(Thread::start);
        referee.start();
    }
}

执行结果:

 三、源码解析

1、类结构

2、Sync 一个静态内部类

//构造方法 CountDownLatch 的构造方法最终调用的是 Sync的构造。
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count); //初始化计数器
        }

        // 获取当前计数器
        int getCount() {
            return getState();
        }

        // 试图在共享模式下获取对象状态
        // 申请资源 如果 数量为 0,则不进行阻塞, 否则进入阻塞
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

       // 试图设置状态来反映共享模式下的一个释放
        // 减少计数器数量,直到数量为0,
        protected boolean tryReleaseShared(int releases) {

             // 死循环保证最终这个状态值能设置成功
            for (;;) {
                // 获取当前状态值,CountDownLatch中的锁存器计数
                int c = getState();
                // 如果状态为0,表示CountDownLatch中的锁存器计数为0,就直接返回
                if (c == 0)
                    return false;
                // 如果状态不为0,则将状态减一
                int nextc = c-1;
                /*
                 * 更新state值,即锁存器计数值,通过CAS保证线程安全
                 * CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做
                 * compareAndSetState(c, nextc)方法:
                 *      c:表示预期值
                 *       nextc:要更新的值
                 *   在此处,表示c==getState()时返回true,并更新state值为nextc
                 *   如果c!=getState()时,表示已经有其他线程更新了state值,
                 *   所以这里不进行更新,直接返回false,通过死循环再重新获取最新的getState()值
                 */
                if (compareAndSetState(c, nextc))
                    // 返回状态是否为0判断,为0表示锁存器计数为0,可以唤醒await的进程了
                    // 这里唤醒进程的操作也是通过AQS进行实现的
                    return nextc == 0;
            }
        }
    }

 从源码可知 CountDownLatch 内部是通过 AQS 的共享模式实现的

3、await() 和 await(long timeout, TimeUnit unit) 方法

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

 此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
     return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非超时或者被中断。

4、countDown() 方法

此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程 

 /**   
     * count值减 1,直到计数达到零,释放所有等待的线程 。
     *      
     *  <p>如果当前计数大于零,则递减。
     *   如果新计数为零,则重新启用所有等待的线程 ,达到线程调度的目的。
     *      
     * <p>如果当前计数等于零,则没有任何反应。
     */    
public void countDown() {
    sync.releaseShared(1);
}

注意:

  1. 初始化CountDownLatch 时的计数器必须大于0,只有当计数器等于 0 的时候,调用 await() 方法是不会阻塞。
  2. 任务的结束并不一定代表着正常的结束,有可能是在运算的过程中出现错误,因此为了能够正确地执行countDown(),需要将该方法的调用放在finally代码块中,否则就会出现主线程(任务)await()方法永远不会退出阻塞的问题。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CountDownLatch是Java并发编程中的一种同步工具类。它的作用是允许一个或多个线程等待其他线程完成操作。CountDownLatch的构造函数接收一个计数值,当计数值变为0时,等待的线程就会被唤醒。使用CountDownLatch可以实现多线程并发编程中的线程协作。 与使用join方法等待多个线程执行完毕不同,CountDownLatch相对灵活。可以通过调用countDown方法来减少计数,唤醒被阻塞的线程。这使得多个线程可以同时进行,并在一定条件下等待其他线程完成后再继续执行。 CountDownLatch一般称为闭锁或计数器,它是Java并发编程中的一种多线程同步工具。它属于AQS(AbstractQueuedSynchronizer)体系的一员,可以实现线程之间的协作和同步操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java多线程之并发工具类](https://download.csdn.net/download/weixin_38713057/13756829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [java多线程并发CountDownLatch](https://blog.csdn.net/weixin_42419762/article/details/116220340)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [多线程编程CountDownLatch](https://blog.csdn.net/jiangxiayouyu/article/details/118107977)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值