【JUC】循环屏障 CyclicBarrier 详解

news2024/11/15 19:54:50

前言

jdk 中提供了许多的并发工具类,大家可能比较熟悉的有CountDownLatch,主要用来阻塞一个线程运行,直到其他线程运行完毕。而 jdk 还有一个功能类似并发工具类CyclicBarrier,你知道它的作用吗?和CountDownLatch有什么区别呢?

对于 CountDownLatch 不了解的可以参考# CountDownLatch源码硬核解析

介绍和使用

CyclicBarrier,循环屏障,用来进行线程协作,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,会触发自己运行,运行完后,屏障又会开门,所有被屏障拦截的线程又可以继续运行。所以CyclicBarrier 是可以重用的。

为了更好的理解,我们举个例子,如下图所示:

我们将屏障想成栅栏,5 个线程想成 5 头猪。5 头猪开始往前跑,直到都跑到栅栏前,栅栏开始做个自己的任务,比如看看猪多重。然后打开栅栏,猪又会继续跑,跑到下一个栅栏,就这样循环....

API 介绍

构造方法

  • public CyclicBarrier(int parties): 创建parties个线程任务的循环CyclicBarrier

  • public CyclicBarrier(int parties, Runnable barrierAction): 当parties个线程到到屏障出,自己执行任务barrierAction

常用 API

  • int await():线程调用 await 方法通知 CyclicBarrier 本线程已经到达屏障

基本使用

我们将上面猪猪的例子通过CyclicBarrier简单做一个实现。

@Slf4j(topic = "c.CyclicBarrierPig")public class CyclicBarrierPig {
    public static void main(String[] args) {        ExecutorService service = Executors.newFixedThreadPool(5);        CyclicBarrier barrier = new CyclicBarrier(5, () -> {            System.out.println("主人看看哪个猪跑最快,最肥...");        });
        // 循环跑3次        for (int i = 0; i < 3; i++) {            // 5条猪开始跑            for(int j = 0; j<5; j++) {                int finalJ = j;                service.submit(() -> {                   log.info("pig{} is run .....", finalJ);                    try {                        // 随机时间,模拟跑花费的时间                        Thread.sleep(new Random().nextInt(5000));                        log.info("pig{} reach barrier .....", finalJ);                        barrier.await();                    } catch (InterruptedException | BrokenBarrierException e) {                        e.printStackTrace();                    }                });            }        }        service.shutdown();    }}

复制代码

运行结果:

实现原理

我们已经明白CyclicBarrier的基本使用了,那我们看看它是如何实现的。

成员属性

  • 全局锁:利用可重入锁实现的工具类

// barrier 实现是依赖于Condition条件队列,condition 条件队列必须依赖lock才能使用private final ReentrantLock lock = new ReentrantLock();// 线程挂起实现使用的 condition 队列,当前代所有线程到位,这个条件队列内的线程才会被唤醒private final Condition trip = lock.newCondition();

复制代码

  • 线程数量

// 代表多少个线程到达屏障开始触发线程任务private final int parties;  // 表示当前“代”还有多少个线程未到位,初始值为 partiesprivate int count;      

复制代码

  • 当前代中最后一个线程到位后要执行的任务

private final Runnable barrierCommand;

复制代码

  • 代, 也是用标记一次循环

// 表示 barrier 对象当前 代private Generation generation = new Generation();private static class Generation {    // 表示当前“代”是否被打破,如果被打破再来到这一代的线程 就会直接抛出 BrokenException 异常    // 且在这一代挂起的线程都会被唤醒,然后抛出 BrokerException 异常。    boolean broken = false;}

复制代码

构造方法

public CyclicBarrie(int parties, Runnable barrierAction) {    // 因为小于等于 0 的 barrier 没有任何意义    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;    this.count = parties;    // 可以为 null    this.barrierCommand = barrierAction;}

复制代码

成员方法

  • await():阻塞等待所有线程到位

public int await() throws InterruptedException, BrokenBarrierException {    try {        return dowait(false, 0L);    } catch (TimeoutException toe) {        throw new Error(toe); // cannot happen    }}
// timed:表示当前调用await方法的线程是否指定了超时时长,如果 true 表示线程是响应超时的// nanos:线程等待超时时长,单位是纳秒private int dowait(boolean timed, long nanos) {    final ReentrantLock lock = this.lock;    // 加锁    lock.lock();    try {        // 获取当前代        final Generation g = generation;
        // 【如果当前代是已经被打破状态,则当前调用await方法的线程,直接抛出Broken异常】        if (g.broken)            throw new BrokenBarrierException();    // 如果当前线程被中断了,则打破当前代,然后当前线程抛出中断异常        if (Thread.interrupted()) {            // 设置当前代的状态为 broken 状态,唤醒在 trip 条件队列内的线程            breakBarrier();            throw new InterruptedException();        }
        // 逻辑到这说明,当前线程中断状态是 false, 当前代的 broken 为 false(未打破状态)                // 假设 parties 给的是 5,那么index对应的值为 4,3,2,1,0        int index = --count;        // 条件成立说明当前线程是最后一个到达 barrier 的线程,【需要开启新代,唤醒阻塞线程】        if (index == 0) {            // 栅栏任务启动标记            boolean ranAction = false;            try {                final Runnable command = barrierCommand;                if (command != null)                    // 启动触发的任务                    command.run();                // run()未抛出异常的话,启动标记设置为 true                ranAction = true;                // 开启新的一代,这里会【唤醒所有的阻塞队列】                nextGeneration();                // 返回 0 因为当前线程是此代最后一个到达的线程,index == 0                return 0;            } finally {                // 如果 command.run() 执行抛出异常的话,会进入到这里                if (!ranAction)                    breakBarrier();            }        }
        // 自旋,一直到条件满足、当前代被打破、线程被中断,等待超时        for (;;) {            try {                // 根据是否需要超时等待选择阻塞方法                if (!timed)                    // 当前线程释放掉 lock,【进入到 trip 条件队列的尾部挂起自己】,等待被唤醒                    trip.await();                else if (nanos > 0L)                    nanos = trip.awaitNanos(nanos);            } catch (InterruptedException ie) {                // 被中断后来到这里的逻辑                                // 当前代没有变化并且没有被打破                if (g == generation && !g.broken) {                    // 打破屏障                    breakBarrier();                    // node 节点在【条件队列】内收到中断信号时 会抛出中断异常                    throw ie;                } else {                    // 等待过程中代变化了,完成一次自我打断                    Thread.currentThread().interrupt();                }            }      // 唤醒后的线程,【判断当前代已经被打破,线程唤醒后依次抛出 BrokenBarrier 异常】            if (g.broken)                throw new BrokenBarrierException();
            // 当前线程挂起期间,最后一个线程到位了,然后触发了开启新的一代的逻辑            if (g != generation)                return index;      // 当前线程 trip 中等待超时,然后主动转移到阻塞队列            if (timed && nanos <= 0L) {                breakBarrier();                // 抛出超时异常                throw new TimeoutException();            }        }    } finally {        // 解锁        lock.unlock();    }}

复制代码

  • breakBarrier():打破 Barrier 屏障

private void breakBarrier() {    // 将代中的 broken 设置为 true,表示这一代是被打破了,再来到这一代的线程,直接抛出异常    generation.broken = true;    // 重置 count 为 parties    count = parties;    // 将在trip条件队列内挂起的线程全部唤醒,唤醒后的线程会检查当前是否是打破的,然后抛出异常    trip.signalAll();}

复制代码

  • nextGeneration():开启新的下一代

private void nextGeneration() {    // 将在 trip 条件队列内挂起的线程全部唤醒    trip.signalAll();    // 重置 count 为 parties    count = parties;
    // 开启新的一代,使用一个新的generation对象,表示新的一代,新的一代和上一代【没有任何关系】    generation = new Generation();}

复制代码

和 CountDownLatch 的区别

相同点

二者都能让一个或多个线程阻塞等待,都可以用在多个线程间的协调,起到线程同步的作用。

不同点

  1. CountDownLatch 的计数器只能使用一次,而 CyclicBarrier 的计数器可以反复 使用。

  2. CountDownLatch.await 一般阻塞工作线程,所有的进行预备工作的线程执行 countDown,而 CyclicBarrier 通过工作线程调用 await 从而自行阻塞,直到所有工作线程达到指定屏障,所有的线程才会返回各自执行自己的工作。

  3. CyclicBarrier 还可以提供一个 barrierAction,合并多线程计算结果。

  4. CountDownLatch 会阻塞主线程,CyclicBarrier 不会阻塞主线程,只会阻塞子线程。

总结

本文讲解了CyclicBarrier 的基本功能和使用,同时讲解了它大致的实现。关于CyclicBarrier 具体有什么使用场景,你可能还是比较迷惑,我再举个例子,比如CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。

如果本文对你有帮助的话,请留下一个赞吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/56745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL学习记录(6)索引02

2.6、索引的使用 2.6.1、最左前缀法则 如果索引了多列&#xff08;联合索引&#xff09;&#xff0c;要遵循最左前缀法则。最左前缀法则指的是查询从索引的最左列开始&#xff0c;并且不跳过索引的列&#xff0c;如果跳跃某一列&#xff0c;索引将会部分失效&#xff08;后面…

大二Web课程设计:HTML+CSS学校静态网页设计——南京师范大学泰州学院(11页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

[附源码]Python计算机毕业设计SSM开心鲜花系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java外卖小程序管理系统源码带小程序前端+后端搭建教程

这套系统已经完成了线下配送的大部分功能 技术架构 技术框架&#xff1a;springboot ssm mysql redis 运行环境&#xff1a;IntelliJ IDEA 2022 jdk1.8 Mysql5.7.4 maven nginx 宝塔面板 后端搭建教程 1.下载源码后打开小皮面板&#xff0c;安装mysql5.7数据库&#x…

LIO-SAM源码解析(二):代码结构

1. 代码整体框架 首先看看工程目录结构&#xff0c;主要有五个文件&#xff0c;分别是utility.h&#xff0c;featureExtraction.cpp&#xff0c; imageProjection.cpp&#xff0c;imuPreintegration.cpp&#xff0c;mapOptmization.cpp LIO-SAM/config/params.yaml …

如何借助低代码开发平台 YonBuilder 填补应用开发 “产能缺口”?

低代码平台本身是各类模型、引擎的重新组合&#xff0c;目的是为了填补应用开发需求远超开发者产能这一缺口&#xff0c;那如何理解填补应用开发需求和开发者产能的缺口&#xff1f; 完成一个企业级的应用复杂度随着技术的进步、需求的细化、业务要求的变化并不是逐渐降低而是…

原创|对接三方服务商回调鉴权的程序代码设计

文章目录一、背景二、详细设计1、UML设计2、程序设计2.1、AuthenticateActionEnum2.2、AuthenticateDispatcher2.3、BaseAuthenticateContext<Request>2.3.1、ActivityStatusChangeAuthenticateContext2.3.2、VodEventNotifyAuthenticateContext2.4、AbstractAuthenticat…

【Python+Appium】自动化测试(十一)location与size获取元素坐标

目录 前言 一&#xff0c;获取元素坐标的方法 1&#xff0c;size获取元素的宽、高 2&#xff0c;location获取元素左上角坐标 3&#xff0c;由此可以计算出元素其他的坐标 二&#xff0c;使用场景 结语 前言 appium做app自动化测试过程中&#xff0c;有时需要获取控件元…

git clean 命令详解

1. git clean 介绍 2. git clean 使用 3. clean 和 reset 命令 1. git clean 介绍 git clean 命令用于删除工作目录中没有被 tracked 的文件 这个命令很多人都不知道&#xff0c;也不去用它&#xff0c;而是通过手动去删除这些文件 这个命令一定要慎用&#xff0c;当你对这…

智源社区AI周刊No.108:Meta发布玩外交游戏的Cicero,登Science;Neuralink实现猴子意念打字...

汇聚每周AI热点&#xff0c;不错过重要资讯&#xff01;欢迎扫码&#xff0c;关注并订阅智源社区AI周刊。Meta发布外交谈判策略系统Cicero&#xff1a;模拟人类参与战略决策&#xff0c;已登《Science》期刊近日&#xff0c;Meta发布人工智能系统Cicero&#xff0c;该系统结合神…

【云原生】k8s 管理平台 rancher

文章目录一、概述二、Rancher 架构三、安装 Rancher1&#xff09;安装Helm2&#xff09;安装ingress-controller3&#xff09;为 Rancher 创建命名空间4&#xff09;选择 SSL 配置5&#xff09;安装 cert-manager6&#xff09;通过 Helm 安装 Rancher2&#xff09;添加 Helm Ch…

电子签名-为你的数据签字画押

博主&#xff1a;爱码叔 个人博客站点&#xff1a; icodebook 公众号&#xff1a;漫话软件设计 专注于软件设计与架构、技术管理。擅长用通俗易懂的语言讲解技术。对技术管理工作有自己的一定见解。文章会第一时间首发在个站上&#xff0c;欢迎大家关注访问&#xff01; 更多密…

[附源码]Python计算机毕业设计SSM绝味鸭脖连锁店信息系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

2023年天津天狮学院专升本市场营销专业《管理学》考试大纲

2023天津天狮学院高职升本科市场营销专业入学考试《管理学》考试大纲一、考试性质 《管理学》专业课程考试是天津天狮学院市场营销专业高职升本入学考试的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《管理学》考试大纲的编…

xcode登陆appleid报错:连接appleid服务器时出错

问题现象&#xff1a; xcode登陆appleid时一直报错&#xff1a;连接appleid服务器时出错 更换连接的wifi也一样报错&#xff0c;怀疑是因为Charles代理导致 解决办法&#xff1a; 网络连接 → 高级 → 代理 → 去掉 勾选【网页代理(HTTP)】

自动化之路:telnet的自动登录脚本

前言 为了测试telnet&#xff0c;首先&#xff0c;要保证系统已经安装了telnet&#xff0c;并且还得有一个端口能用,就是1-65536那个PORT。 一 搭建telenet环境并测试 1 首先查看telnet运行状态&#xff1a; lkmaoubuntu:~$ netstat -a | grep telnet lkmaoubuntu:~$ 输出为…

Linux 文本处理命令 - chmod

chmod命令&#xff1a;修改文件或目录的权限 chmod命令使用数字修改文件权限 Linux 系统中&#xff0c;文件的基本权限由 9 个字符组成&#xff0c;以 rwxrw-r-x 为例&#xff0c;我们可以使用数字来代表各个权限&#xff0c;各个权限与数字的对应关系如下&#xff1a; r --…

【c/c++算法】曼哈顿算法简单运用

✨曼哈顿算法&#x1f996;1.曼哈顿距离算法&#xff1a;&#x1f422;2.例题&#xff1a;打印菱形曼哈顿算法讲解&#xff1a;&#x1f995;3.曼哈顿算法例题解释&#xff1a;&#x1f996;1.曼哈顿距离算法&#xff1a; 我们很早以前就学过了两点间距离公式&#xff0c;欧式…

vue纯手写思维导图,拒绝插件(代码cv即用)

vue纯手写思维导图&#xff0c;拒绝插件(代码cv即用) 已完成功能点&#xff1a;折叠、放大、缩小、移动 后续增加功能点&#xff1a;添加、删除 先看结果&#xff1a; 有这么个需求&#xff0c;按照层级关系&#xff0c;把表格放在思维导图上&#xff0c;我第一时间想到用插件…

【Matplotlib绘制图像大全】(十六):Matplotlib绘制虚线折线图

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…