当前位置: 首页 > news >正文

深入理解CyclicBarrier

文章目录

      • 1. 概念
      • 2. CylicBarier使用简单案例
      • 3. 源码

1. 概念

CyclicBarrier 字面意思回环栅栏(循环屏障),通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。CyclicBarrier 作用是让一组线程相互等待,当达到一个共同点时,所有之前等待的线程再继续执行,且 CyclicBarrier 功能可重复使用。
在这里插入图片描述

2. CylicBarier使用简单案例

public class Main {public static void main(String[] args) throws  InterruptedException{CyclicBarrier cyclicBarrier=new CyclicBarrier(3);for (int i = 0; i < 5; i++) {new Thread(()->{try{System.out.println(Thread.currentThread().getName()+"开始等待其它线程");//阻塞直到指定方法的数量调用这个方法就会停止阻塞cyclicBarrier.await();System.out.println(Thread.currentThread().getName()+"开始执行");Thread.sleep(5000);System.out.println(Thread.currentThread().getName()+"执行完毕");} catch (Exception e) {e.printStackTrace();}}).start();}}
}

在这里插入图片描述

可以发现只有3个线程继续执行,剩余两个线程被阻塞

3. 源码

  • 构造方法
//这个构造方法有两个参数,分别是parties和一个任务,parties代表着屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。当阻塞的线程达到parties的数量时,就会执行barrieAction这个任务
public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;//使用两个变量存储parties,这也是parties可以复用的根本原因this.count = parties;this.barrierCommand = barrierAction;}public CyclicBarrier(int parties) {this(parties, null);}
  • 重要方法
 public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}}public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException {return dowait(true, unit.toNanos(timeout));}

源码分析要点

1. 一组现场在触发屏障之前互相等待,最后一个线程到达屏障后唤醒逻辑是如何实现的
2. 栅栏循环是如何实现的
3. 条件队列到同步队列的转换实现逻辑

await()方法

   public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}}

发现里面实际逻辑调用的是dowait(false, 0L)方法

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {//定义了一个ReentrantLockfinal ReentrantLock lock = this.lock;lock.lock();try {final Generation g = generation;if (g.broken)throw new BrokenBarrierException();if (Thread.interrupted()) {breakBarrier();throw new InterruptedException();}//更新count方法int index = --count;if (index == 0) {  // trippedboolean ranAction = false;try {final Runnable command = barrierCommand;if (command != null)command.run();ranAction = true;nextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}// loop until tripped, broken, interrupted, or timed outfor (;;) {try {if (!timed)//进入条件队列trip进行阻塞trip.await();else if (nanos > 0L)nanos = trip.awaitNanos(nanos);} catch (InterruptedException ie) {if (g == generation && ! g.broken) {breakBarrier();throw ie;} else {Thread.currentThread().interrupt();}}if (g.broken)throw new BrokenBarrierException();if (g != generation)return index;if (timed && nanos <= 0L) {breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();}}

上面方法最核心的就是更新count,然后判断count是否为0,如果为0就开始执行唤醒逻辑(这里先不考虑),如果不为0就会进入trip这个条件队列进行阻塞,下面分析线程是如何进行条件队列阻塞的。

//这是AQS类的一个方法public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();Node node = addConditionWaiter();int savedState = fullyRelease(node);int interruptMode = 0;//判断当亲线程是不是同步队列,不是直接调用park进行阻塞while (!isOnSyncQueue(node)) {LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);}

往条件等待队列中添加节点就是下面这句代码

 Node node = addConditionWaiter();
 private Node addConditionWaiter() {//获得条件队列的最后一个结点Node t = lastWaiter;if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}//如果为空就新创建一个节点Node node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)//如果当前单向队列为空,直接让新创建的节点成为头节点firstWaiter = node;else//否则就放到尾节点的后面t.nextWaiter = node;//让尾指针指向当前节点lastWaiter = node;//返回当前节点return node;}

addConditionWaiter实际是AQS的内部类ConditionObject中实现的

public class ConditionObject implements Condition, java.io.Serializable {private static final long serialVersionUID = 1173984872572414699L;//条件队列的第一个节点private transient Node firstWaiter;//条件队列的最后一个节点private transient Node lastWaiter;public ConditionObject() { }private Node addConditionWaiter() {Node t = lastWaiter;if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}//如果条件队列为空,创建一个新的节点Node node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)//让新创建的节点成为头节点和尾节点firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node;}private void doSignal(Node first) {do {if ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;first.nextWaiter = null;} while (!transferForSignal(first) &&(first = firstWaiter) != null);}/*** Removes and transfers all nodes.* @param first (non-null) the first node on condition queue*/private void doSignalAll(Node first) {lastWaiter = firstWaiter = null;do {Node next = first.nextWaiter;first.nextWaiter = null;transferForSignal(first);first = next;} while (first != null);}private void unlinkCancelledWaiters() {Node t = firstWaiter;Node trail = null;while (t != null) {Node next = t.nextWaiter;if (t.waitStatus != Node.CONDITION) {t.nextWaiter = null;if (trail == null)firstWaiter = next;elsetrail.nextWaiter = next;if (next == null)lastWaiter = trail;}elsetrail = t;t = next;}}

节点入队后就继续执行 public final void await() throws InterruptedException方法,当调用await()方法,我们需要释放持有的锁,也就是执行下面这句代码:

int savedState = fullyRelease(node);
 final int fullyRelease(Node node) {boolean failed = true;try {//获取state标记(独占锁如果state从0-1表示释放锁,从1-0表示占用锁int savedState = getState();if (release(savedState)) {failed = false;return savedState;} else {throw new IllegalMonitorStateException();}} finally {if (failed)node.waitStatus = Node.CANCELLED;}}
  public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

释放锁后回到await()方法,调用下面代码进行实际阻塞

 while (!isOnSyncQueue(node)) {LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}

上面就队线程阻塞以及入队的原理分析,下面分析count减到0,后是如何执行线程唤醒的,核心代码是:

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {if (index == 0) {  // trippedboolean ranAction = false;try {final Runnable command = barrierCommand;if (command != null)command.run();ranAction = true;//开始下一轮屏障nextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}

nextGeneration的代码如下:

    private void nextGeneration() {//唤醒条件队列的所有节点trip.signalAll();// 恢复count值count = parties;generation = new Generation();}

signalAll()唤醒条件队列中所有的节点

public class ConditionObject implements Condition, java.io.Serializable {
......private void doSignalAll(Node first) {//首尾节点置为nulllastWaiter = firstWaiter = null;do {//获取首节点的下一个节点Node next = first.nextWaiter;//然后将first的nextWaiter指针置为空first.nextWaiter = null;//实现头部出队的节点怎么进入同步队列transferForSignal(first);//然后开始迭代处理下一个节点first = next;} while (first != null);}
......
}

下面分析头部出队的节点进入同步队列的逻辑

final boolean transferForSignal(Node node) {//使用CAS操作修改节点的状态if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;//节点入同步队列Node p = enq(node);int ws = p.waitStatus;if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//p节点的前驱节点置换为-1,这样就可以唤醒node节点,然后调用park进行阻塞LockSupport.unpark(node.thread);return true;}

相关文章:

深入理解CyclicBarrier

文章目录 1. 概念2. CylicBarier使用简单案例3. 源码 1. 概念 CyclicBarrier 字面意思回环栅栏&#xff08;循环屏障&#xff09;&#xff0c;通过它可以实现让一组线程等待至某个状态&#xff08;屏障点&#xff09;之后再全部同时执行。叫做回环是因为当所有等待线程都被释放…...

微信小程序 - 格式化操作 moment.js格式化常用使用方法总结大全

格式化操作使用 1. 首先&#xff0c;下载一个第三方库 moment npm i moment --save 注&#xff1a;在微信小程序中无法直接npm 下载 导入 的&#xff08;安装一个就需要构建一次&#xff09; 解决&#xff1a;菜单栏 --> 工具 --> 构建 npm 点击即可&#xff08;会…...

学习pytorch18 pytorch完整的模型训练流程

pytorch完整的模型训练流程 1. 流程1. 整理训练数据 使用CIFAR10数据集2. 搭建网络结构3. 构建损失函数4. 使用优化器5. 训练模型6. 测试数据 计算模型预测正确率7. 保存模型 2. 代码1. model.py2. train.py 3. 结果tensorboard结果以下图片 颜色较浅的线是真实计算的值&#x…...

电子学会C/C++编程等级考试2021年09月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:抓牛 农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000)。农夫有两种移动方式: 1、从X移动到X-1或X+1,每次移动花费一分钟 2、从X移动到2*X,每…...

Halcon联合winform显示以及处理

在窗口中添加窗体和按钮&#xff0c;并在解决方案资源管理器中调加了导入Halcon导出的.cs文件&#xff0c;运行出现下图的问题&#xff1a; 问题1&#xff1a;CS0017 程序定义了多个入口点。使用/main(指定包含入口点的类型&#xff09;进行编译。 解决方案1.&#xff1a; 右…...

【设计模式-4.3】行为型——责任链模式

说明&#xff1a;本文介绍设计模式中行为型设计模式中的&#xff0c;责任链模式&#xff1b; 审批流程 责任链模式属于行为型设计模式&#xff0c;关注于对象的行为。责任链模式非常典型的案例&#xff0c;就是审批流程的实现。如一个报销单的审批流程&#xff0c;根据报销单…...

单片机语言--C51语言的数据类型以及存储类型以及一些基本运算

C51语言 本文主要涉及C51语言的一些基本知识&#xff0c;比如C51语言的数据类型以及存储类型以及一些基本运算。 文章目录 C51语言一、 C51与标准C的比较二、 C51语言中的数据类型与存储类型2.1、C51的扩展数据类型2.2、数据存储类型 三、 C51的基本运算3.1 算术运算符3.2 逻辑…...

《每天一个Linux命令》 -- (5)通过sshkey密钥登录服务器

欢迎阅读《每天一个Linux命令》系列&#xff01;在本篇文章中&#xff0c;将介绍通过密钥生成&#xff0c;使用公钥连接管理服务器。 概念 SSH 密钥是用于安全地访问远程服务器的一种方法。SSH 密钥由一对密钥组成&#xff1a;公钥和私钥。公钥存储在远程服务器上&#xff0c;…...

kubernetes的服务发现(二)

如前面的文章我们说了&#xff0c;kubernetes的服务发现是服务端发现模式。它有一个服务注册中心&#xff0c;使用DNS作为服务的注册表。每个集群都会运行一个DNS服务&#xff0c;默认是CoreDNS服务。每个服务都会在这个DNS中注册。注册的大致过程&#xff1a; 1、向kube-apise…...

【矩阵论】Chapter 4—特征值和特征向量知识点总结复习

文章目录 1 特征值和特征向量2 对角化3 Schur定理和正规矩阵4 Python求解 1 特征值和特征向量 定义 设 σ \sigma σ为数域 F F F上线性空间 V V V上的一个线性变换&#xff0c;一个非零向量 v ∈ V v\in V v∈V&#xff0c;如果存在一个 λ ∈ F \lambda \in F λ∈F使得 σ (…...

Linux 进程地址空间

知识回顾 在 C 语言的学习过程中&#xff0c;我们知道内存是可以被划分为栈区&#xff0c;堆区&#xff0c;全局数据区&#xff0c;字符常量区&#xff0c;代码区的。他的空间排布可能是下面的样子&#xff1a; 其中&#xff0c;全局数据区&#xff0c;可以划分为已初始化全局…...

websocket vue操作

let websocket: WebSocket; /** websocket测试 */ function connectWebsocket() {if (typeof WebSocket "undefined") {console.log("您的浏览器不支持WebSocket");return;}// let ip window.location.hostname ":8080";let ip "10.192…...

腾讯云CentOS8 jenkins war安装jenkins步骤文档

腾讯云CentOS8 jenkins war安装jenkins步骤文档 一、安装jdk 1.1 上传jdk-11.0.20_linux-x64_bin.tar.gz 1.2 解压jdk安装包文件 tar -zxvf jdk*.tar.gz 1.3 在/usr/local 目录下创建java目录 cd /usr/local mkdir java 1.4 切到java目录&#xff0c;把jdk解压文件改名为jd…...

Linux: glibc: net/if.h vs linux/if.h

最近看到一段代码改动,用net/if.h替换了linux/if.h。仔细看了看这两个的区别: https://stackoverflow.com/questions/20082433/what-is-the-difference-between-linux-if-h-and-net-if-h 从网上搜了一下看到如下的一个编译错误,如果同时使用这两个if.h文件,需要将net/if.h…...

使用Android Studio导入Android源码:基于全志H713 AOSP,方便解决编译、编码问题

文章目录 一、 篇头二、 操作步骤2.1 编译AOSP AS工程文件2.2 将AOSP导入Android Studio2.3 切到Project试图2.4 等待index结束2.5 下载缺失的JDK 1.82.6 导入完成 三、 导入AS的好处3.1 本文案例演示源码编译错误AS对比同文件其余地方的调用AS错误提示依赖AS做错误修正 一、 篇…...

python random详解

文章目录 random简单示例1. 生成随机浮点数&#xff1a;2. 生成指定范围内的随机整数&#xff1a;3. 从序列中随机选择元素&#xff1a;4. 打乱序列顺序&#xff1a; 常用的方法及其解释和例子&#xff1a;1. random()&#xff1a;该方法返回一个0到1之间的随机浮点数。例如&am…...

java-两个列表进行比较,判断那些是需要新增的、删除的、和更新的

文章目录 前言两个列表进行比较&#xff0c;判断那些是需要新增的、删除的、和更新的 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实…...

【WPF.NET开发】WPF中的对话框

目录 1、消息框 2、通用对话框 3、自定义对话框 实现对话框 4、打开对话框的 UI 元素 4.1 菜单项 4.2 按钮 5、返回结果 5.1 模式对话框 5.2 处理响应 5.3 非模式对话框 Windows Presentation Foundation (WPF) 为你提供了自行设计对话框的方法。 对话框是窗口&…...

NLP项目实战01之电影评论分类

介绍&#xff1a; 欢迎来到本篇文章&#xff01;在这里&#xff0c;我们将探讨一个常见而重要的自然语言处理任务——文本分类。具体而言&#xff0c;我们将关注情感分析任务&#xff0c;即通过分析电影评论的情感来判断评论是正面的、负面的。 展示&#xff1a; 训练展示如下…...

一款可无限扩展的软件定时器开源框架项目代码

摘自链接 时间片轮询架构如何稳定高效实现&#xff0c;取代传统的标志位判断方式&#xff0c;更优雅更方便地管理程序的时间触发操作。 可以在STM32单片机上运行。...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...

[特殊字符] 手撸 Redis 互斥锁那些坑

&#x1f4d6; 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作&#xff0c;想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁&#xff0c;也顺便跟 Redisson 的 RLock 机制对比了下&#xff0c;记录一波&#xff0c;别踩我踩过…...

边缘计算网关提升水产养殖尾水处理的远程运维效率

一、项目背景 随着水产养殖行业的快速发展&#xff0c;养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下&#xff0c;而且难以实现精准监控和管理。为了提升尾水处理的效果和效率&#xff0c;同时降低人力成本&#xff0c;某大型水产养殖企业决定…...