• 多线程并发之CountDownLatch阻塞等待


    1. 简介

    CountDownLatchcount down是倒数的意思,latch则是门闩、锁住的含义。整体含义可以理解为倒数的门栓。CountDownLatch的作用也是如此,在构造CountDownLatch的时候需要传入一个整数n(必须>0),在这个整数“倒数”到0之前,主线程需要等待在门口,而这个“倒数”过程则是由各个执行线程驱动的,每个线程执行完一个任务“倒数”一次。总结来说,CountDownLatch的作用就是等待其他的线程都执行完任务,必要时可以对各个任务的执行结果进行汇总,然后主线程才继续往下执行。

    CountDownLatch主要有两个方法:countDown()await()countDown()方法用于使计数器减一,其一般是执行任务的线程调用,await()方法则使调用该方法的线程处于等待状态,其一般是主线程调用。这里需要注意的是,countDown()方法并没有规定一个线程只能调用一次,当同一个线程调用多次countDown()方法时,每次都会使计数器减一;另外,await()方法也并没有规定只能有一个线程执行该方法,如果多个线程同时执行await()方法,那么这几个线程都将处于等待状态,并且以共享模式享有同一个锁。

    2. 方法API

    方法:

    方法说明
    await()使当前线程进入同步队列进行等待,直到latch的值被减到0或者当前线程被中断,当前线程就会被唤醒。
    await(long timeout, TimeUnit unit)等待timeout时间后,count的值还不是0,不再等待,那么将继续执行
    countDown()使latch的值减1如果减到了0,则会唤醒所有等待在这个latch上的线程。
    getCount()获得latch的数值。

    3. 使用

    3.1 await()

    示例:

     CountDownLatch count = new CountDownLatch(3);
            new Thread(()->{
                //处理业务1
                try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown();//确保每个任务执行完递减
                }
            }, "t1").start();
            new Thread(()->{
                //处理业务2
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown();//确保每个任务执行完递减
                }
            }, "t2").start();
            new Thread(()->{
                //处理业务3
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown(); //确保每个任务执行完递减
                }
            }, "t3").start();
    
            long startTime = System.currentTimeMillis();
            count.await(); // 等待任务执行
            long endTime = System.currentTimeMillis();
            System.out.println("任务执行完成,耗时:" + (endTime - startTime) + "毫秒");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    结果:

    在这里插入图片描述

    3.2 boolean await(long timeout, TimeUnit unit)

    boolean await(long timeout, TimeUnit unit)示例:

      CountDownLatch count = new CountDownLatch(3);
            new Thread(()->{
                //处理业务1
                try { TimeUnit.SECONDS.sleep(1);
                    System.out.println("task1 over");} catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown();//确保每个任务执行完递减
                }
            }, "t1").start();
            new Thread(()->{
                //处理业务2
                try { TimeUnit.SECONDS.sleep(2);
                    System.out.println("task2 over");} catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown();//确保每个任务执行完递减
                }
            }, "t2").start();
            new Thread(()->{
                //处理业务3
                try { TimeUnit.SECONDS.sleep(3);
                    System.out.println("task3 over");} catch (InterruptedException e) { e.printStackTrace(); } finally {
                    count.countDown(); //确保每个任务执行完递减
                }
            }, "t3").start();
    
            long startTime = System.currentTimeMillis();
            boolean await = count.await(2, TimeUnit.SECONDS);// 指定等待时间,如果当前有任务未执行完成则返回false
            System.out.println("所有任务是否执行完成:" + (await ? "是" : "否"));
            System.out.println("计数器值为:" + count.getCount());
            long endTime = System.currentTimeMillis();
            System.out.println("任务执行完成,耗时:" + (endTime - startTime) + "毫秒");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    分析:

    开启三个线程去执行任务,任务1、任务2、任务3耗时依次为1s、2s、3s
    计数器await等待2s,如果2s后计数器值不为0(即三个任务中有任务未执行完成),那么就返回false。可以用在一些比较耗时长的任务上,例如调用第三方接口、业务线比较长,当超过指定时间后就当作失败处理,避免服务一直处于等待阻塞状态。

    结果:

    在这里插入图片描述

    4. CountDownLatch和Thread.join()方法的区别

    • 1、CountDownLatch的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似join() 方法,但其提供了比join()更加灵活的API。

    • 2、CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作。 而 join() 的实现原理是不停检查join线程是否存活,如果join 线程存活则让当前线程永远等待。所以两者之间相对来说还是CountDownLatch使用起来较为灵活。

    5. CountDownLatch的不足

    CountDownLatch一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

    6. 扩展

    如果采用多线程异步任务Future,通过CompletableFuture.allOf也可以实现同样的效果,阻塞等待任务执行结果,参考文章多线程Future,CompletableFuture

  • 相关阅读:
    java.lang.Exception: No runnable methods
    常用的日期格式整理
    小鱼送你个URDF模板|省出来时间过七夕
    ssh2-sftp-client实现前端项目自动部署
    LinkWeChat 私域管理平台基于企业微信的开源 SCRM
    onnx文件及其结构、正确导出onnx、onnx解析器
    proxy代理服务
    含文档+PPT+源码等]精品微信小程序小说阅读器+后台管理系统|前后分离VUE[包运行成功]微信小程序项目源码Java毕业设计
    荐读 | 图计算的查询模式有哪些?
    Windows Server安全配置
  • 原文地址:https://blog.csdn.net/weixin_43847283/article/details/125556371