Java教程

Java并发工具学习(九)——CountDownLatch和CyclicBarrier

本文主要是介绍Java并发工具学习(九)——CountDownLatch和CyclicBarrier,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 前言
  • CountDownLatch
  • CyclicBarrier
  • 二者的区别
  • 总结

前言

继续简单总结一下Java中并发流程控制的工具类,这一篇小结一下CountDownLatch和CyclicBarrier

CountDownLatch

Latch中文是门闩的意思,CountDown是倒数,倒数到0,门闩开启。从名称可以看出这个工具的使用场景。比如在某多多上拼团购物,5人成团,等到成团了才有资格购买。

通常CountDownLatch有两种常见的用法

1、一个线程等待其他多个线程倒数为0时,再进入下一步

在这里插入图片描述
上图形象表示了CountDownLatch的一种使用方法,由其他线程倒数到0了之后,图中的Ta线程才能继续进行下一步。
简单代码实例:模拟一个产品需要5个质检员完成质检才能发布

/**
 * autor:liman
 * createtime:2021/11/28
 * comment:模拟工厂质检
 * 一个产品的发布,需要5个人完成质检
 */
@Slf4j
public class CountDownLatchDemo01 {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(5);//需要5个人完成质检
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int number = i+1;
            Runnable runnable=new Runnable(){
                @Override
                public void run() {
                    try {
                        //模拟质检的时间
                        long checkTime = (long) (Math.random() * 10000);
                        Thread.sleep(checkTime);
                        System.out.println("No."+number+"完成了质检,耗时:"+checkTime+"ms");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //一个线程完成质检,就调用countDown方法,直到CountDownLatch的数值为0,则主线程就可以执行
                        countDownLatch.countDown();
                    }
                }
            };
            executorService.submit(runnable);
        }
        System.out.println("等待5个质检员检查完毕");
        //主线程等待5个子线程完成质检,调用CountDownLatch的await方法
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("所有人都完成了工作,产品可以对外发布");
        //停止线程池
        //判断线程池是否停止
        executorService.shutdown();
        while(!executorService.isTerminated()){
            //线程池没有执行完成任务,主线程就空转,直到线程池运行结束
        }
    }
}

2、多个线程等待一个线程倒数为0,再进行下一步。

这里通过一个比较复杂的综合实例来说明,如以下代码,模拟了一个运动长跑的简单实例,采用了两个CountDownLatch对象。相关代码解释已在注释中

/**
 * autor:liman
 * createtime:2021/11/28
 * comment:模拟运动会的运动员跑步
 * 多个运动员等待统一指令起跑
 * 同时所有运动员都到终点的时候才能结束比赛
 */
@Slf4j
public class CountDownLatchDemo02 {

    public static void main(String[] args) throws InterruptedException {
        //初始化启动的CountDownLatch,指定个数为1
        CountDownLatch beginCountDownLatch = new CountDownLatch(1);
        //初始化结束的CountDownLatch,指定个数为5
        CountDownLatch endCountDownLatch = new CountDownLatch(5);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i+1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("No."+no+"准备完毕,等待发令枪发令");
                    try {
                        beginCountDownLatch.await();//子线程会在这里阻塞,等待主线程发出指令
                        System.out.println("No."+no+"开始跑步");
                        long runTime = (long) (Math.random() * 10000);
                        Thread.sleep(runTime);//模拟随机的跑步时长,执行完毕之后,表示跑到终点
                        System.out.println("No."+no+"到达终点,耗时:"+runTime+"ms");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //到达终点,发出信号
                        endCountDownLatch.countDown();
                    }
                }
            };
            executorService.submit(runnable);
        }
        //主线程其实就是裁判,模拟裁判准备工作
        Thread.sleep(5000);
        System.out.println("发令枪响,所有运动员开始跑步");
        beginCountDownLatch.countDown();
        endCountDownLatch.await();//主线程阻塞,裁判员继续等待所有运动员到达终点。
        System.out.println("所有人都到终点,比赛结束");
        //判断线程池是否停止
        executorService.shutdown();
        while(!executorService.isTerminated()){
            //线程池没有执行完成任务,主线程就空转,直到线程池运行结束
        }
    }
}

上述实例中的等待5个运动员到达终点,才能结束比赛,这一点和第一种场景,有五个质检员都完成质检,产品才能发布是一样的。

基于上述两个常用的使用场景,其实CountDownLatch也支持一组线程等待另一组线程执行完毕后的情况。

需要注意的是:CountDownLatch是不能复用的,如果要重新计数,可以考虑用CyclicBarrier或者重新创建CountDownLatch。

CyclicBarrier

Cyclic中文为循环,Barrier是栅栏的意思,循环栅栏说明这个是可复用的。这个其实在实际的使用场景中是和CountDownLatch很相似的。

直接上实例吧

/**
 * autor:liman
 * createtime:2021/11/28
 * comment:循环栅栏的实例
 * 相比于CountDownLatch,CyclicBarrier是可重用的
 */
@Slf4j
public class CyclicBarrierDemo {

    public static void main(String[] args) {
        //实例化一个CyclicBarrier,第二个参数是所有的线程都到齐了之后,CyclicBarrier的操作
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人都到了,大家统一出发");
            }
        });

        for (int i = 0; i < 10; i++) {
            //启动每一个线程
            new Thread(new Task(i, cyclicBarrier)).start();
        }
    }

    static class Task implements Runnable {

        private int id;
        private CyclicBarrier cyclicBarrier;

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程" + id + "现在前往集合地点");
            try {
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("线程" + id + "到了集合地点,开始等待其他线程到达");
                cyclicBarrier.await();//等待其他线程到达。
                System.out.println("线程" + id + "出发");//所有线程都就位了,就会一起执行这个代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果
在这里插入图片描述

二者的区别

二者使用场景很相似,但还是存在些许差异
1、二者作用不同。CyclicBarrier要等到固定数量的线程到达了指定位置才能继续执行(CountDownLatch好像也有这个作用),但是CountDownLatch只需要等待设定的数值为0之后,就可以执行,毕竟一个线程中也可以多次进行倒数,CountDownLatch关注的是事件,而CyclicBarrier关注的才是线程。
2、可重用性不同。CountDownLatch在倒数到0时,就不能再次使用了,除非新建实例,而CyclicBarrier是可重用的,上述CyclicBarrier的实例中,虽然CyclicBarrier初始化的数值为5,但是循环启动了10个线程,依旧可重新使用。

总结

简单梳理了一下二者的使用场景,后续开始总结AQS的内容

这篇关于Java并发工具学习(九)——CountDownLatch和CyclicBarrier的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!