回环屏障CyclicBarrier
上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDownLatch的await和countDown方法就会立刻返回,就没有作用了,那么反正是一个计数器,为什么不能重复使用呢?于是就出现了这篇说的CyclicBarrier,它的状态可以被重用;
一.简单例子
用法其实和CountDownLatch差不多,也就是一个计数器,当计数器的值变为0之后,就会把阻塞的线程唤醒:
package com.example.demo.study; import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class Study0216 {
// 注意这里的构造器,第一个参数表示计数器初始值
// 第二个参数表示当计数器的值变为0的时候就触发的任务
static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
System.out.println("cyclicBarrier task ");
}); public static void main(String[] args) {
// 新建两个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 线程1放入线程池中
pool.submit(() -> {
try {
System.out.println("Thread1----await-begin");
cyclicBarrier.await();
System.out.println("Thread1----await-end");
} catch (Exception e) {
e.printStackTrace();
}
});
// 线程2放到线程池中
pool.submit(() -> {
try {
System.out.println("Thread2----await-begin");
cyclicBarrier.await();
System.out.println("Thread2----await-end");
} catch (Exception e) {
e.printStackTrace();
}
});
// 关闭线程池,此时还在执行的任务会继续执行
pool.shutdown();
}
}

我们再看看CyclicBarrier的复用性,这里比如有一个任务,有三部分组成,分别是A,B,C,然后创建两个线程去执行这个任务,必须要等到两个线程都执行完成A部分,然后才能开始执行B,只有两个线程都执行完成B部分,才能执行C:
package com.example.demo.study; import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class Study0216 {
// 这里的构造器,只有一个参数,表示计数器初始值
static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); public static void main(String[] args) {
// 新建两个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 线程1放入线程池中
pool.submit(() -> {
try {
System.out.println("Thread1----stepA-start");
cyclicBarrier.await(); System.out.println("Thread1----stepB-start");
cyclicBarrier.await(); System.out.println("Thread1----stepC-start"); } catch (Exception e) {
e.printStackTrace();
}
});
// 线程2放到线程池中
pool.submit(() -> {
try {
System.out.println("Thread2----stepA-start");
cyclicBarrier.await(); System.out.println("Thread2----stepB-start");
cyclicBarrier.await(); System.out.println("Thread2----stepC-start");
} catch (Exception e) {
e.printStackTrace();
}
});
// 关闭线程池,此时还在执行的任务会继续执行
pool.shutdown();
}
}

二.基本原理
我们看看一些重要属性:
public class CyclicBarrier {
//这个内部类只有一个boolean值
private static class Generation {
boolean broken = false;
}
//独占锁
private final ReentrantLock lock = new ReentrantLock();
//条件变量
private final Condition trip = lock.newCondition();
//保存线程的总数
private final int parties;
//这是一个任务,通过构造器传递一个任务,当计数器变为0之后,就可以执行这个任务
private final Runnable barrierCommand;
//这类内部之后一个boolean的值,表示屏障是否被打破
private Generation generation = new Generation();
//计数器
private int count;
}
构造器:
//我们的构造器初始值设置的是parties
public CyclicBarrier(int parties) {
this(parties, null);
}
//注意,这里开始的时候是count等于parties
//为什么要有两个变量呢?我们每次调用await方法的时候count减一,当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方法:
public int await() throws InterruptedException, BrokenBarrierException {
try {
//调用的是dowait方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//假设count等于3,有三个线程都在调用这个方法,默认超时时间为0,那么首每次都只有一个线程可以获取锁,将count减一,不为0
//就会到下面的for循环中扔到条件队列中挂起;直到第三个线程调用这个dowait方法,count减一等于0,那么当前线程执行任务之后,
//就会唤醒条件变量中阻塞的线程,并重置count为初始值3
private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException, TimeoutException {
//获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//g中只有一个boolean值
final Generation g = generation;
//如果g中的值为true的时候,抛错
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程中断,就抛错
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//count减一,再赋值给index
int index = --count;
//如果index等于0的时候,说明所有的线程已经到屏障点了,就可以
if (index == 0) { // tripped
boolean ranAction = false;
try {
//执行当前线程的任务
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//唤醒其他因为调用了await方法阻塞的线程
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
//能到这里来,说明是count不等于0,也就是还有的线程没有到屏障点
for (;;) {
try {
//wait方法有两种情况,一种是设置超时时间,一种是不设置超时时间
//这里就是对超时时间进行的一个判断,如果设置的超时时间为0,则会在条件队列中无限的等待下去,直到被唤醒
//设置了超时时间,那就等待该时间
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();
}
}
//唤醒其他因为调用了await方法阻塞的线程
private void nextGeneration() {
//唤醒条件变量中所有线程
trip.signalAll();
//重置count的值
count = parties;
generation = new Generation();
}
private void breakBarrier() {
generation.broken = true;
//重置count为初始值parties
count = parties;
//唤醒条件队列中的所有线程
trip.signalAll();
}
回环屏障CyclicBarrier的更多相关文章
- CyclicBarrier回环屏障深度解析
1. 前沿 从上一节的CountDownLatch的学习,我们发现其只能使用一次,当state递减为0后,就没有用了,需要重新新建一个计数器.那么我们有没有可以复用的计数器呢?当然,JUC包给我们提供 ...
- 回环栅栏CyclicBarrier
通过它可以实现让一组线程等待至某个状态之后再全部同时执行.叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用.我们暂且把这个状态就叫做barrier,当调用await()方 ...
- 并发编程-concurrent指南-回环栅栏CyclicBarrier
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行. java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步 ...
- thread_CyclicBarrier回环栅栏
CyclicBarrier回环栅栏,字面意思是可循环使用(Cyclic)的屏障(Barrier).通过它可以实现让一组线程等待至某个状态之后再全部同时执行. 它要做的事情是,让一组线程到达一个屏障(也 ...
- ORB-SLAM(六)回环检测
上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...
- SharePoint回环检查(Loopback Check)相关问题
Loopback Check(回环检查)本来不是一个SharePoint问题,是Windows Server为了增强自身安全性在Server 2003 SP1后引入的一个功能, 在近几个月中导致了一系 ...
- 关于STM32 CAN回环可用,正常不可用情况分析
1.回环下应该与GPIO无关 2.GPIO是否初始化正确,时钟启用 3.是否复用,AFIO时钟是否启用 4.回环下是否有CAN_Tx应该有输出 5.终端电阻是否有 6.CAN收发器电路电压是否正常 7 ...
- linux回环网卡驱动设计
回环网卡驱动 1.回环网卡和普通网卡的区别是他是虚拟的不是实际的物理网卡,它相当于把普通网卡的发送端和接收端短接在一起. 2.在内核源代码里的回环网卡程序(drivers/net/loopback.c ...
- VMware配置回环地址用于测试
我们在开发过程中,很可能需要一台服务器用于测试,在这种环境下,我们很可能需要用到vmware来构建这样的开发环境.但如果当前处在一个离线,或是不在网内的环境下,我们所搭建的环境有可能无法 ...
随机推荐
- Blazor client-side + webapi (.net core 3.1) 添加jwt验证流程(非host)第二步 添加Identity
添加Identity数据上下文 安装nuget包:Microsoft.AspNetCore.Identity.EntityFrameworkCore 创建ApplicationDbContext类 创 ...
- 多字节与Unicode
编码知识 一.Unicode与多字节(ANSI ) (1)Windows中,Unicode也称为宽字节,多字节也称为窄字节; VS中默认使用Unicode编码,在项目属性>>配置属性> ...
- PHP0018:PHP 图像处理
- 流程图GGEditor 之 自定义节点相关属性
自定义节点 注册 -- registerNode 我们通过以下接口往 G6 全局注册节点: // 注册节点 G6.registerNode(name, { // 绘制 draw(item) { r ...
- 基于云开发开发 Web 应用(二):界面 UI 开发
工作量分析 在我们进行这部分开发的时候,接下来我们需要进行相应的功能安排和分类. 简单看来,我需要开发 3 个页面: 首页:首页负责用户默认访问. 列表页:列表页面则是在搜索过程中,如果有多个结果,则 ...
- Vue中在template标签中进行判断时注意比较元素
(一)比较的元素,一个是data元素,另外一个是常量,如下图所示: 编译正常,运行正常,效果在期望中,会显示Hello World,结果如下: (二)比较的元素,一个是data元素,另外一个是cons ...
- 输入python -m pip install --upgrade pip更新pip版本失败的解决办法
突然发现输入 python -m pip install --upgrade pip 无法升级pip,试了很多种其他更新pip的命令之后还是无效,遂采用简单暴力的解决办法 首先,直接输入pip uni ...
- Mybaits(10)N+1问题
N+1问题 从上面的例子日志中我们可以看到所有级联都成功了,但是引发了性能问题,例如我们在查询雇员的信息和工作任务信息,此时体检表和工牌信息就是多余,我们没必要查询一次.如果想日志体现的那样,取出了所 ...
- 【daily】Java泛型 - 返回父类的子类
一.栗子 public class GenericityInher { //error: Type mismatch: cannot convert from ArrayList<Child&g ...
- 用友UAP NC 单据新增数据时抛出"流程平台缓存中不存在该单据或交易类型=HB06"
正常单据新增时,抛出异常"流程平台缓存中不存在该单据或交易类型=HB06"