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

CountDownLatch与CyclicBarrier原理剖析

1.CountDownLatch

1.1 什么是CountDownLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

1.2 CountDownLatch与join

使用join同样可以达到线程同步的效果,但是调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。

而且如果我们使用线程池的话,就没有办法直接调用线程的join方法了。所有另一方面来说,CountDownLatch要比join更加灵活。

1.3 CountDownLatch的基本使用

public class CountDownLatchTest {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(5);for (int i=0; i<=5; i++) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 运行");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}}}).start();}System.out.println("等待子线程运行结束");latch.await();System.out.println("子线程运行结束");}
}

这里主线程会阻塞在latch.await(),直到CountDownLatch技术为0。

1.4 CountDownLatch原理剖析

CountDownLatch类图

请添加图片描述

从上面我们可以直到Sync继承了AQS类,CountDownLatch又持有一个成员变量Sync,所有我们可以直到CountDownLatch是基于AQS实现的。

通过构造方法我们又可以得知CountDownLatch计数器的值赋给了AQS的state变量。

public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}Sync(int count) {setState(count);
}

await()

当线程调用await方法以后,当前线程会被阻塞,直到下面情况之一时才会返回:

  • 当所有的线程都调用了CountDownLatchcountDown方法后,也就是计数器为0时
  • 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程会抛出异常然后返回
// CountDownLatch的await()实现
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}// AQS中获取共享资源可被中断的方法
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {// 判断当前线程是否已被中断,如果是则抛出异常if (Thread.interrupted())throw new InterruptedException();// state不为0(意味着CountDownLatch还没有减到0)// 则执行AQS的doAcquireSharedInterruptibly方法,让当前线程进入AQS队列if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}// CountDownLatch中的Sync中tryAcquireShared()方法
protected int tryAcquireShared(int acquires) {// 当前state如果为0则返回1否则返回-1return (getState() == 0) ? 1 : -1;
}

由上述代码我们可以知道线程获取资源时可以被中断,并且获取的是共享资源。

名为await的方法还有一个,不过多个参数,也就是指定时间后,调用await(long timeout, TimeUnit unit)的线程会超时而返回false,如果是正常返回的,那么返回值就为true。

public boolean await(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

countDown()

线程调用该方法以后,计数器的值会递减,递减后如果计数器的值为0,那么就会唤醒所有因调用await方法而阻塞的线程,否则什么都不做。

// CountDownLatch中的countDown方法
public void countDown() {sync.releaseShared(1);
}// AQS中的方法
public final boolean releaseShared(int arg) {// 调用Sync实现的tryReleaseShared方法if (tryReleaseShared(arg)) {// AQS中释放资源的方法doReleaseShared();return true;}return false;
}// Sync中重写的tryReleaseShared方法
protected boolean tryReleaseShared(int releases) {// 循环进行CAS操作,将state做减一操作,失败则一直重试for (;;) {// 获得当前的state变量int c = getState();// 如果state已经等于0,那么直接返回falseif (c == 0)return false;// 将state-1int nextc = c-1;// CAS操作修改state,成功以后判断state是否已经为0,为0则返回true,// 再接下来就会调用AQS中的doReleaseShared方法释放资源if (compareAndSetState(c, nextc))return nextc == 0;}
}

2.CyclicBarrier

2.1 什么是CyclicBarrier

从字面理解,CyclicBarrier就是回环屏障的意思,它可以让一组线程达到一个状态后再同时执行。

  • 回环的意思是在所有线程执行完毕以后,会重置CyclicBarrier的状态使它可以被重用
  • 屏障的意思是线程调用await方法后都会被阻塞,这个阻塞点就被称为屏障,等到所有屏障都调用了await方法后,线程们就会冲破屏障继续向下运行

2.2 CyclicBarrier的基本使用

public class CycleBarrierTest {private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {// 当计数器为0时,立即执行@Overridepublic void run() {System.out.println("汇总线程:" + Thread.currentThread().getName() + " 任务合并。");}});public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(2);// 将线程A添加到线程池executorService.submit(new Runnable() {@Overridepublic void run() {try {System.out.println("线程A:" + Thread.currentThread().getName() + "执行任务。");System.out.println("线程A:到达屏障点");cyclicBarrier.await();System.out.println("线程A:退出屏障点");} catch (Exception e) {e.printStackTrace();}}});// 将线程B添加到线程池executorService.submit(new Runnable() {@Overridepublic void run() {try {System.out.println("线程B:" + Thread.currentThread().getName() + "执行任务。");System.out.println("线程B:到达屏障点");cyclicBarrier.await();System.out.println("线程B:退出屏障点");} catch (Exception e) {e.printStackTrace();}}});// 调用线程池的shutdown方法关闭线程池// 该方法会使线程池从RUNNING状态转变为SHUTDOWN状态// SHUTDOWN状态意味着:不再接收新的任务,但是会对任务队列中的任务进行处理executorService.shutdown();}
}

执行结果为:

线程A:pool-1-thread-1执行任务。
线程A:到达屏障点
线程B:pool-1-thread-2执行任务。
线程B:到达屏障点
汇总线程:pool-1-thread-2 任务合并。
线程B:退出屏障点
线程A:退出屏障点

上面的例子说明了多个线程之间是相互等待的,假如计数器值为N,那么随后调用 await 方法的 N–1 个线程都会因为到达屏障点而被阻塞,当第 N 个线程调用 await 后,计 数器值为 0 了,这时候第 N 个线程才会发出通知唤醒前面的 N–1 个线程。也就是当全部 线程都到达屏障点时才能一块继续向下执行。不过这个例子并没有体现出可重用性,不过这个其实也很好理解,就是可以反复使用,感兴趣的同学可以自己去了解一下。

2.3 CyclicBarrier原理剖析

CyclicBarrier类图

请添加图片描述

由类图可知,CyclicBarrier基于独占锁实现,本质还是基于AQS实现的。

  • Generation内部有一个变量broken,用来记录当前屏障是否被打破,因为内部使用重入锁保证了线程安全,所以该属性不需要使用volatile修饰

  • parties用来记录线程个数,意味着parties个线程调用await方法以后,才会“冲破屏障

  • count一开始等于parties,每当有线程调用await方法就会减一,当count为零就意味着所有线程到达了屏障点

使用两个变量的原因就是为了达成CyclicBarrier的复用性,当count计数为0以后,会将parties重新赋值给count,从而进行复用。

public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;
}

同时通过构造函数我们也可以直到,我们可以传递一个任务,而这个任务的执行时机是当所有的线程都到达屏障点以后。

await()

当线程调用await方法被阻塞,直到满足以下条件之一时就会返回:

  • parties个线程调用了await方法,也就是所有线程都到达了屏障点
  • 其他线程调用了该线程的interrupt方法中断了该线程
  • 与当前屏障点关联的Generation对象的broken标志被设置为true时
public int await() throws InterruptedException, BrokenBarrierException {try {// 调用dowait方法阻塞当前线程,第一个参数为false表示第二个参数不生效return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}
}public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException {// 指定timeout后会自动返回return dowait(true, unit.toNanos(timeout));
}

dowait()

该方法实现了CyclicBarrier的核心功能。

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {// 获得锁并进行加锁final ReentrantLock lock = this.lock;lock.lock();try {final Generation g = generation;if (g.broken)throw new BrokenBarrierException();if (Thread.interrupted()) {breakBarrier();throw new InterruptedException();}// 如果index=0就说明所有的线程都到达了屏障点,此时开始执行初始化传递的任务barrierActionint index = --count;if (index == 0) { boolean ranAction = false;try {final Runnable command = barrierCommand;// 执行任务if (command != null)command.run();ranAction = true;// 激活其他因调用await阻塞的线程,并且重置了CyclicBarriernextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}// 如果index!=0for (;;) {try {// 没有设置超时时间的操作if (!timed)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();}
}// 看到这里你应该就明白了为什么CyclicBarrier可以被复用
private void nextGeneration() {// 唤醒条件队列中的阻塞线程trip.signalAll();// 重置CyclicBarriercount = parties;generation = new Generation();
}

3.CountDownLatch与CyclicBarrier

  • CountDownLatch计数器不能重置,CyclicBarrier可以重置循环利用。
  • CountDownLatch是基于AQS的共享模式实现的,CyclicBarrier是基于ReentrantLockCondition实现的。
  • 两者最大的区别是,进行下一步动作的动作实施者是不一样的。这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。

相关文章:

CountDownLatch与CyclicBarrier原理剖析

1.CountDownLatch 1.1 什么是CountDownLatch CountDownLatch是一个同步工具类&#xff0c;用来协调多个线程之间的同步&#xff0c;或者说起到线程之间的通信&#xff08;而不是用作互斥的作用&#xff09;。 CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之…...

NLP中的对话机器人——预训练基准模型

引言 本文是七月在线《NLP中的对话机器人》的视频笔记&#xff0c;主要介绍FAQ问答型聊天机器人的实现。 场景二 上篇文章中我们解决了给定一个问题和一些回答&#xff0c;从中找到最佳回答的任务。 在场景二中&#xff0c;我们来实现&#xff1a; 给定新问题&#xff0c;从…...

C语言学习及复习笔记-【14】C文件读写

14 C文件读写 14.1打开文件 您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件&#xff0c;这个调用会初始化类型 FILE 的一个对象&#xff0c;类型 FILE包含了所有用来控制流的必要的信息。下面是这个函数调用的原型&#xff1a; FILE *fopen( const char…...

模拟退火算法优化灰色

clc; clear; close all; warning off; %% tic T01000; % 初始温度 Tend1e-3; % 终止温度 L200; % 各温度下的迭代次数&#xff08;链长&#xff09; q0.9; %降温速率 X[16.4700 96.1000 16.4700 94.4400 20.0900 92.5400 22.3900 93.3700 25.…...

Pandas怎么添加数据列删除列

Pandas怎么添加数据列 1、直接赋值 # 1、直接赋值df.loc[:, "最高气温"] df["最高气温"].str.replace("℃", "").astype("int32")df.loc[:, "最低气温"] df["最低气温"].str.replace("℃"…...

C++类和对象:构造函数和析构函数

目录 一. 类的六个默认成员函数 二. 构造函数 2.1 什么是构造函数 2.2 编译器自动生成的默认构造函数 2.3 构造函数的特性总结 三. 析构函数 3.1 什么是析构函数 3.2 编译器自动生成的析构函数 3.3 析构函数的特性总结 一. 类的六个默认成员函数 对于任意一个C类&…...

【Stata】从入门到精通.零基础小白必学的教程,一学就fei

视频教程移步&#xff1a;https://www.bilibili.com/video/BV1hK4y1d714/?p4&spm_id_frompageDriver&vd_sourcecc8074e9c81a225f214226065db53d32P3 第二讲 Stata处理数据全流程&#xff08;上&#xff09; P3 - 01:37&#xfeff;内置数据 file example datasets使用…...

【RuoYi优化】调整JVM启动内存

📔 笔记介绍 大家好,千寻简笔记是一套全部开源的企业开发问题记录,毫无保留给个人及企业免费使用,我是作者星辰,笔记内容整理并发布,内容有误请指出,笔记源码已开源,前往Gitee搜索《chihiro-notes》,感谢您的阅读和关注。 作者各大平台直链: GitHub | Gitee | CSD…...

[架构模型]MVC模型详细介绍,并应用到unity中

简介&#xff1a; MVC模式是一种软件架构模式&#xff0c;它将应用程序分为三个主要部分&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;。MVC模式的目标是实现应用程序的松耦合&#xff0c;以便…...

?? JavaScript 双问号(空值合并运算符)

?? JavaScript 双问号&#xff08;空值合并运算符) 一、简述 在网上浏览 JavaScript 代码时或者学习其他代码时&#xff0c;可能会发现有的表达式用了两个问号&#xff08;??&#xff09;如下所示&#xff1a; let username; console.log(username ?? "Guest"…...

作业2.25----通过操作Cortex-A7核,串口输入相应的命令,控制LED灯进行工作

1.通过操作Cortex-A7核&#xff0c;串口输入相应的命令&#xff0c;控制LED灯进行工作 例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输入led2off,开饭led2灯熄灭 5.例如在串口输入led…...

0101基础概念-图-数据结构和算法(Java)

文章目录1 图1.1 定义1.2 4种图模型2 无向图2.1 定义2.2 术语后记1 图 1.1 定义 图是一种非线性的数据结构&#xff0c;表示多对多的关系。 图&#xff08;Graph&#xff09;是由顶点的有穷非空集合和顶点之间边的集合组成&#xff0c;通常表示为&#xff1a;G(V, E)&#xf…...

Linux基础命令和工具使用详解

Linux基础命令和工具使用详解一、grep搜索字符二、find查找文件三、ls 显示文件四、wc命令计算字数五、uptime机器启动时间负载六、ulimit用户资源七、curl http八、scp远程拷贝九、dos2unix和unix2dos十、sed 行处理10.1、简单模式10.2、替换模式十一、awk 列处理11.1、打印某…...

一个好的python文件可以有几种用途?

大家好鸭&#xff01;我是小熊猫~ 这次来带大家浅浅回顾一点python小知识~ 源码资料电子书:点击此处跳转文末名片获取 python文件总共有两种用途&#xff1a; 一种是执行文件另一种是被当做模块导入 编写好的一个python文件可以有两种用途&#xff1a; 1. 脚本&#xff0c;…...

HDFS优化

单节点多块磁盘数据均衡 生成HDFS块均衡计划 hdfs diskbalancer -plan node1 执行均衡计划,node1.plan.json均衡计划文件 hdfs diskbalancer -execute node1.plan.json 查看当前均衡任务的执行情况 hdfs diskbalancer -query node1 取消均衡任务hdfs diskbalancer -cancel nod…...

行测-判断推理-图形推理-样式规律-黑白运算

黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选A考试时&#xff0c;这种题不要先把规律全部推出来&#xff0c;再去做题&#xff0c;太慢了直接看要推的图&#xff0c;通过排除法选答案黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选B…...

java+springboot+vue高校学生医疗保险管理系统

医保管理系统是对与职工健康息息相关的档案进行的系统化、自动化的管理&#xff0c;主要是对职工办理的医疗保险的管理&#xff0c;本系统能够很好的适应社会的需求&#xff0c;最大化的为城镇职工提供服务。医疗保险是国家社会保障体系的重要组成部分&#xff0c;也是社会保险…...

[已解决] AHK 映射 ESC 延迟 500 ms 的严重问题

问题描述 今天发现一个重大bug&#xff0c;我竟然用了一年多都不知道&#xff01; CapsLock::Esc 我的 ahk 脚本将 capslock 映射为 esc&#xff0c;但这在vim环境中&#xff0c;估算响应 500ms。 也就说按下 caps 键&#xff0c;还要等一会&#xff0c;才进入normal模式 如果…...

QML state详解

1.state简介 changes&#xff08;list<Change>&#xff09;&#xff1a;保存当前State下的多个Change对象,比如PropertyChanges、StateChangeScript、ParentChange等。 extend&#xff08;string&#xff09;&#xff1a;表示该状态要在哪个State的基础上进行扩展,当一个…...

一起Talk Android吧(第五百零六回:如何调整组件在约束布局中的角度)

文章目录背景介绍相关属性使用方法示例程序各位看官们大家好&#xff0c;上一回中咱们说的例子是"如何调整组件在约束布局中的大小",这一回中咱们说的例子是"如何调整组件在约束布局中的角度"。闲话休提&#xff0c;言归正转&#xff0c; 让我们一起Talk A…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...

大数据治理的常见方式

大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法&#xff0c;以下是几种常见的治理方式&#xff1a; 1. 数据质量管理 核心方法&#xff1a; 数据校验&#xff1a;建立数据校验规则&#xff08;格式、范围、一致性等&#xff09;数据清洗&…...

欢乐熊大话蓝牙知识17:多连接 BLE 怎么设计服务不会乱?分层思维来救场!

多连接 BLE 怎么设计服务不会乱&#xff1f;分层思维来救场&#xff01; 作者按&#xff1a; 你是不是也遇到过 BLE 多连接时&#xff0c;调试现场像网吧“掉线风暴”&#xff1f; 温度传感器连上了&#xff0c;心率带丢了&#xff1b;一边 OTA 更新&#xff0c;一边通知卡壳。…...

比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表

设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

学习 Hooks【Plan - June - Week 2】

一、React API React 提供了丰富的核心 API&#xff0c;用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素&#xff0c;JSX 会被编译成该函数…...

触发DMA传输错误中断问题排查

在STM32项目中&#xff0c;集成BLE模块后触发DMA传输错误中断&#xff08;DMA2_Stream1_IRQHandler进入错误流程&#xff09;&#xff0c;但单独运行BLE模块时正常&#xff0c;表明问题可能源于原有线程与BLE模块的交互冲突。以下是逐步排查与解决方案&#xff1a; 一、问题根源…...

2025年全国I卷数学压轴题解答

第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos ⁡ x − cos ⁡ ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min ⁡ t max ⁡ x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...