Java多线程进阶(十九)—— J.U.C之synchronizer框架:CyclicBarrier

2023-10-29

微信图片_20180814202118.jpg

本文首发于一世流云专栏: https://segmentfault.com/blog...

一、CyclicBarrier简介

CyclicBarrier是一个辅助同步器类,在JDK1.5时随着J.U.C一起引入。

这个类的功能和我们之前介绍的CountDownLatch有些类似。我们知道,CountDownLatch是一个倒数计数器,在计数器不为0时,所有调用await的线程都会等待,当计数器降为0,线程才会继续执行,且计数器一旦变为0,就不能再重置了。

CyclicBarrier可以认为是一个栅栏,栅栏的作用是什么?就是阻挡前行。
顾名思义,CyclicBarrier是一个可以循环使用的栅栏,它做的事情就是:
让线程到达栅栏时被阻塞(调用await方法),直到到达栅栏的线程数满足指定数量要求时,栅栏才会打开放行。

这其实有点像军训报数,报数总人数满足教官认为的总数时,教官才会安排后面的训练。

可以看下面这个图来理解下:
一共4个线程A、B、C、D,它们到达栅栏的顺序可能各不相同。当A、B、C到达栅栏后,由于没有满足总数【4】的要求,所以会一直等待,当线程D到达后,栅栏才会放行。
clipboard.png

从CyclicBarrier的构造器,我们也可以看出关于这个类的一些端倪,CyclicBarrier有两个构造器:

构造器一:
clipboard.png
这个构造器的参数parties就是之前说的需要满足的计数总数。

构造器二:
clipboard.png
这个构造器稍微特殊一些,除了指定了计数总数外,传入了一个Runnable任务。

Runnable任务其实就是当最后一个线程到达栅栏时,后续立即要执行的任务。

比如,军训报数完毕后,总人数满足了要求,教官就会开始命令大家执行下一个任务,这个【下一个任务】就是这里的Runnable。

二、CyclicBarrier示例

我们来看一个CyclicBarrier的示例,来理解下它的功能。

假设现在有这样一个场景:
5个运动员准备跑步比赛,运动员在赛跑前会准备一段时间,当裁判发现所有运动员准备完毕后,就举起发令枪,比赛开始。
这里的起跑线就是屏障,运动员必须在起跑线等待其他运动员准备完毕。
public class CyclicBarrierTest {
    public static void main(String[] args) {

        int N = 5;  // 运动员数
        CyclicBarrier cb = new CyclicBarrier(N, new Runnable() {
            @Override
            public void run() {
                System.out.println("****** 所有运动员已准备完毕,发令枪:跑!******");
            }
        });

        for (int i = 0; i < N; i++) {
            Thread t = new Thread(new PrepareWork(cb), "运动员[" + i + "]");
            t.start();
        }

    }


    private static class PrepareWork implements Runnable {
        private CyclicBarrier cb;

        PrepareWork(CyclicBarrier cb) {
            this.cb = cb;
        }

        @Override
        public void run() {

            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName() + ": 准备完成");
                cb.await();          // 在栅栏等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

执行上面的程序,可能的输出结果如下:

运动员[3]: 准备完成
运动员[1]: 准备完成
运动员[0]: 准备完成
运动员[2]: 准备完成
运动员[4]: 准备完成
****** 所有运动员已准备完毕,发令枪:跑!******

从输出可以看到,线程到达栅栏时会被阻塞(调用await方法),直到到达栅栏的线程数满足指定数量要求时,栅栏才会打开放行。

CyclicBarrier对异常的处理

我们知道,线程在阻塞过程中,可能被中断,那么既然CyclicBarrier放行的条件是等待的线程数达到指定数目,万一线程被中断导致最终的等待线程数达不到栅栏的要求怎么办?

CyclicBarrier一定有考虑到这种异常情况,不然其它所有等待线程都会无限制地等待下去。
那么CyclicBarrier是如何处理的呢?

我们看下CyclicBarrier的await()方法:

public int await() throws InterruptedException, BrokenBarrierException {
    //...
}

可以看到,这个方法除了抛出InterruptedException异常外,还会抛出BrokenBarrierException

BrokenBarrierException表示当前的CyclicBarrier已经损坏了,可能等不到所有线程都到达栅栏了,所以已经在等待的线程也没必要再等了,可以散伙了。

出现以下几种情况之一时,当前等待线程会抛出BrokenBarrierException异常:

  1. 其它某个正在await等待的线程被中断了
  2. 其它某个正在await等待的线程超时了
  3. 某个线程重置了CyclicBarrier(调用了reset方法,后面会讲到)

另外,只要正在Barrier上等待的任一线程抛出了异常,那么Barrier就会认为肯定是凑不齐所有线程了,就会将栅栏置为损坏(Broken)状态,并传播BrokenBarrierException给其它所有正在等待(await)的线程。

我们来对上面的例子做个改造,模拟下异常情况:

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

        int N = 5;  // 运动员数
        CyclicBarrier cb = new CyclicBarrier(N, new Runnable() {
            @Override
            public void run() {
                System.out.println("****** 所有运动员已准备完毕,发令枪:跑!******");
            }
        });

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            Thread t = new Thread(new PrepareWork(cb), "运动员[" + i + "]");
            list.add(t);
            t.start();
            if (i == 3) {
                t.interrupt();  // 运动员[3]置中断标志位
            }
        }

        Thread.sleep(2000);
        System.out.println("Barrier是否损坏:" + cb.isBroken());
    }


    private static class PrepareWork implements Runnable {
        private CyclicBarrier cb;

        PrepareWork(CyclicBarrier cb) {
            this.cb = cb;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + ": 准备完成");
                cb.await();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + ": 被中断");
            } catch (BrokenBarrierException e) {
                System.out.println(Thread.currentThread().getName() + ": 抛出BrokenBarrierException");
            }
        }
    }
}

可能的输出结果:

运动员[0]: 准备完成
运动员[2]: 准备完成
运动员[1]: 准备完成
运动员[3]: 准备完成
运动员[3]: 被中断
运动员[4]: 准备完成
运动员[4]: 抛出BrokenBarrierException
运动员[0]: 抛出BrokenBarrierException
运动员[1]: 抛出BrokenBarrierException
运动员[2]: 抛出BrokenBarrierException
Barrier是否损坏:true

这段代码,模拟了中断线程3的情况,从输出可以看到,线程0、1、2首先到达Brrier等待。
然后线程3到达,由于之前设置了中断标志位,所以线程3抛出中断异常,导致Barrier损坏,此时所有已经在栅栏等待的线程(0、1、2)都会抛出BrokenBarrierException异常。
此时,即使再有其它线程到达栅栏(线程4),都会抛出BrokenBarrierException异常。

注意:使用 CyclicBarrier时,对异常的处理一定要小心,比如线程在到达栅栏前就抛出异常,此时如果没有重试机制,其它已经到达栅栏的线程会一直等待(因为没有还没有满足总数),最终导致程序无法继续向下执行。

三、CyclicBarrier原理

CyclicBarrier的构造

CyclicBarrier有两个构造器:
CyclicBarrier cb = new CyclicBarrier(10);

clipboard.png

构造器内部的各个字段含义如下:

字段名 作用
parties 栅栏开启需要的到达线程总数
count 剩余未到达的线程总数
barrierCommand 最后一个线程到达后执行的任务

CyclicBarrier的内部结构

CyclicBarrier 并没有自己去实现AQS框架的API,而是利用了ReentrantLockCondition

public class CyclicBarrier {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition trip = lock.newCondition();

    // 栅栏开启需要的到达线程总数
    private final int parties;

    // 最后一个线程到达后执行的任务
    private final Runnable barrierCommand;

    // 剩余未到达的线程总数
    private int count;

    // 当前轮次的运行状态
    private Generation generation = new Generation();

    // ...
}

需要注意的是generation这个字段:
clipboard.png

我们知道,CyclicBarrier 是可以循环复用的,所以CyclicBarrier 的每一轮任务都需要对应一个generation 对象。
generation 对象内部有个broken字段,用来标识当前轮次的CyclicBarrier 是否已经损坏。
nextGeneration方法用来创建一个新的generation 对象,并唤醒所有等待线程,重置内部参数。

CyclicBarrier的核心方法

我们先来看下await方法:
clipboard.png
可以看到,无论有没有超时功能,内部都是调了dowait这个方法:
clipboard.png

dowait方法并不复杂,一共有3部分:

  1. 判断栅栏是否已经损坏或当前线程已经被中断,如果是会分别抛出异常;
  2. 如果当前线程是最后一个到达的线程,会尝试执行最终任务(如果构造CyclicBarrier对象时有传入Runnable的话),执行成功即返回,失败会破坏栅栏;
  3. 对于不是最后一个到达的线程,会在Condition队列上等待,为了防止被意外唤醒,这里用了一个自旋操作。

破坏栅栏用的是breakBarrier方法:
clipboard.png

再来看下CyclicBarrierreset方法:
clipboard.png

该方法先破坏栅栏,然后开始下一轮(新建一个generation对象)。

四、CyclicBarrier接口/类声明

类声明:
clipboard.png

构造器声明:
clipboard.png

接口声明:
clipboard.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程进阶(十九)—— J.U.C之synchronizer框架:CyclicBarrier 的相关文章

  • Grails 3.x bootRun 失败

    我正在尝试在 grails 3 1 11 中运行一个项目 但出现错误 失败 构建失败并出现异常 什么地方出了错 任务 bootRun 执行失败 进程 命令 C Program Files Java jdk1 8 0 111 bin java
  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • Play框架运行应用程序问题

    每当我尝试运行使用以下命令创建的新 Web 应用程序时 我都会收到以下错误Play http www playframework org Error occurred during initialization of VM Could no
  • 给定两个 SSH2 密钥,我如何检查它们是否属于 Java 中的同一密钥对?

    我正在尝试找到一种方法来验证两个 SSH2 密钥 一个私有密钥和一个公共密钥 是否属于同一密钥对 我用过JSch http www jcraft com jsch 用于加载和解析私钥 更新 可以显示如何从私钥 SSH2 RSA 重新生成公钥
  • 制作一个交互式Windows服务

    我希望我的 Java 应用程序成为交互式 Windows 服务 用户登录时具有 GUI 的 Windows 服务 我搜索了这个 我发现这样做的方法是有两个程序 第一个是服务 第二个是 GUI 程序并使它们进行通信 服务将从 GUI 程序获取
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 无法解析插件 Java Spring

    我正在使用 IntelliJ IDEA 并且我尝试通过 maven 安装依赖项 但它给了我这些错误 Cannot resolve plugin org apache maven plugins maven clean plugin 3 0
  • 禁止的软件包名称:java

    我尝试从数据库名称为 jaane 用户名 Hello 和密码 hello 获取数据 错误 java lang SecurityException Prohibited package name java at java lang Class
  • 如何将 pfx 文件转换为 jks,然后通过使用 wsdl 生成的类来使用它来签署传出的肥皂请求

    我正在寻找一个代码示例 该示例演示如何使用 PFX 证书通过 SSL 访问安全 Web 服务 我有证书及其密码 我首先使用下面提到的命令创建一个 KeyStore 实例 keytool importkeystore destkeystore
  • 为什么HashMap不能保证map的顺序随着时间的推移保持不变

    我在这里阅读有关 Hashmap 和 Hashtable 之间的区别 http javarevisited blogspot sg 2010 10 difference Between hashmap and html http javar
  • simpleframework,将空元素反序列化为空字符串而不是 null

    我使用简单框架 http simple sourceforge net http simple sourceforge net 在一个项目中满足我的序列化 反序列化需求 但在处理空 空字符串值时它不能按预期工作 好吧 至少不是我所期望的 如
  • 静态变量的线程安全

    class ABC implements Runnable private static int a private static int b public void run 我有一个如上所述的 Java 类 我有这个类的多个线程 在里面r
  • 在 Maven 依赖项中指定 jar 和 test-jar 类型

    我有一个名为 commons 的项目 其中包含运行时和测试的常见内容 在主项目中 我添加了公共资源的依赖项
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 将 List 转换为 JSON

    Hi guys 有人可以帮助我 如何将我的 HQL 查询结果转换为带有对象列表的 JSON 并通过休息服务获取它 这是我的服务方法 它返回查询结果列表 Override public List
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • 一个月的学习体会

    匆忙的一个月的学习已经结束了 感觉自己也从刚开始的冲劲满满开始有了疲惫感 特别是最近由于疫情经常半夜2 3点 56点做核酸 导致睡眠不足 上课就开始打瞌睡 头一次产生如此重的疲惫感 但是钱一个月的学习总的来说 收获还是挺大的 就自我感觉到的
  • 最大子数组和(Python)

    给一个整数数组 nums 请找出一个具有最大和的连续子数组 子数组最少包含一个元素 返回其最大和 子数组是数组中的一个连续部分 示例 1 输入 nums 2 1 3 4 1 2 1 5 4 输出 6 解释 连续子数组 4 1 2 1 的和最
  • chrome浏览器91版本SameSite by default cookies被移除后的解决方案,Chrome中跨域POST请求无法携带Cookie的解决方案

    周一早上一打开电脑准备开发项目时候 突然发现网站登录跳转有异常 怎么都登录不上一直跳回登录页 通过抓包排除了后端的原因后 发现后端的set cookie没有效果 突然想起Chrome禁用第三方Cookies的计划 打开Edge的更新记录发现
  • Ubuntu下自动启动终端并运行脚本或命令

    1 2 command填写示例 gnome terminal x home river startupRun sh 3 startupRun sh示例 date sleep 5 date gnome terminal mnt hgfs E
  • Druid关闭自动重试

    设置两个属性就可以了 来自druid GitHub connectionErrorRetryAttempts 0 breakAfterAcquireFailure true
  • 正大国际期货:恒指交易如何避免频繁止损?

    正大国际金融控股有限公司 简称 正大国际 成立于2019年11月4日 为香港证监会辖下之持牌法团 证监会中央编号 BOP620 从事第2类及第5类受规管活动及期货合约交易及就期货合约提供意见 主要从事商品期货经纪 金融期货经纪业务 致力于提
  • DATEDIFF() 函数——返回两个日期之间的时间

    定义和用法 DATEDIFF 函数返回两个日期之间的时间 语法 DATEDIFF datepart startdate enddate startdate 和 enddate 参数是合法的日期表达式 datepart 参数可以是下列的值 实
  • HarmonyOS开发:那些开发中常见的问题汇总(一)

    前言 本来这篇文章需要讲述静态共享包如何实现远程依赖和上传以及关于静态共享包私服的搭建 非常遗憾的告诉大家 由于组织管理申请迟迟未通过 和部分文档官方权限暂未开放 关于这方面的讲解需要延后了 大概需要等到2024年第一季度 也就是来年 毕竟
  • windows Server 2012 R2安装部署

    Windows Server 2012 R2 是基于Windows8 1 以及Windows RT 8 1 界面的新一代 Windows Server 操作系统 提供企业级数据中心和混合云解决方案 易于部署 具有成本效益 以应用程序为重点
  • Delphi 通过TNetHTTPClient访问http,最新解析快手无水印视频地址链接方法

    一 解析快手无水印视频链接原理 共分三个步骤 1 通过视频分享获得视频地址短链接 如 https www kuaishou com f X7tIV0jIivYUyTk 2 通过TNetHTTPClient重定向获得视频地址长链接 如 htt
  • 把桌面从C盘改到D盘,结果直接让D盘变成了桌面,改回去发现图标变少了

    昨天晚上我一时兴起想把我电脑桌面的位置改到D盘 然后我就打开了它的属性 把位置改了 点了 应用 后弹出来一个弹窗 询问我 是否要将所有文件从原位置移动到新位置 我点了 是 其实正常来讲只要你那个新位置是个文件夹就可以 但是我当时不知道 我没
  • JavaSHA-256加解密

    Java中可以使用java security MessageDigest类来进行SHA 256加密 以下是一个使用SHA 256加密字符串的示例代码 import java security MessageDigest public cla
  • IBM发布基于OpenStack的服务

    原文地址 http www csdn net article 2013 03 05 2814349 IBM lunch service based on OpenStack 时隔13年后 IBM再一次拥抱开源 这一次 是被称为21世纪Lin
  • spi设备驱动

    include
  • iOS第三方支付集成-微信支付

    序言 说来惭愧 终于有支付的需求给我做了 哇嘎嘎 开动 文章尽量写的详细点 从自身出发 希望能给大家一点帮助 欢迎大佬指正 支付流程 步骤1 用户在商户APP中选择商品 提交订单 选择微信支付 步骤2 商户后台收到用户支付单 调用微信支付统
  • hdlm 5.9在hacmp中的配置

    hdlm 5 9是hds多路径软件最新版本的 它与以前版本有不小的改进 比如以前一个ldev 如果有4个通道 那么在os上面可以看到4个hdisk 然后这个hdisk再组成一个dlmfdrv 在5 9中只有一个hdisk 没有dlmfdrv
  • 音视频编码类型

    H264 格式介绍 avcc 前四个字节表示nalu的size 大端 Annex B 0x000001或者0x00000001开始码 nalu针对0x000000 0x000001 0x000002和0x000003插入0x03防竞争字节
  • IDEA导入Eclipse项目步骤详解

    IDEA导入Eclipse项目步骤详解 文章目录 IDEA导入Eclipse项目步骤详解 首先在idea里file gt new gt Project from Existing Sources 选中到要导入的项目 这里我选用创建新的 Cl
  • 情感分析概述

    情感分析主要研究观点挖掘 倾向性分析等 一 为什么需要观点挖掘和倾向性分析 文本信息主要包括两类 客观性事实 主观性观点 但是已有的文本分析方法主要侧重在客观性文本内容的分析和挖掘 二 什么是观点挖掘与倾向性分析 观点挖掘与倾向性分析就是从
  • Java多线程进阶(十九)—— J.U.C之synchronizer框架:CyclicBarrier

    本文首发于一世流云专栏 https segmentfault com blog 一 CyclicBarrier简介 CyclicBarrier是一个辅助同步器类 在JDK1 5时随着J U C一起引入 这个类的功能和我们之前介绍的Count