线程屏障CyclicBarrier实现原理
生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具CyclicBarrier就能够完成这种功能。本篇从源码方面,简要分析CyclicBarrier的实现原理。
使用示例
public class CyclicBarrierTest {
public static void main(String[] args) {
//屏障,阻拦3个线程
CyclicBarrier cyclicBarrier = new CyclicBarrier();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1正在执行");
try {
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程1运行结束,时间: " + System.currentTimeMillis());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2正在执行");
try {
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程2运行结束,时间: " + System.currentTimeMillis());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3正在执行");
try {
//线程3阻塞2秒,测试效果
Thread.sleep();
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程3运行结束,时间: " + System.currentTimeMillis());
}
}).start();
}
}
执行结果如下:
线程1正在执行
线程2正在执行
线程3正在执行
线程1运行结束,时间:
线程3运行结束,时间:
线程2运行结束,时间:
可以看到线程1,2,3在同一个时间结束。
源码分析
主要成员:
private final ReentrantLock lock = new ReentrantLock(); private final Condition trip = lock.newCondition(); private int count;
CyclicBarrier主要借助重入锁ReentrantLock和Condition实现。count初始值等于CyclicBarrier实例化指明的等待线程数量,用于等待线程计数。
主要方法await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock(); // 1
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count; // 2
if (index == ) { // 3
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration(); // 4
return ;
} finally {
if (!ranAction)
breakBarrier(); // 5
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await(); // 6
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
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(); // 7
}
}
- 对当前对象加锁
- 每个线程获得锁,执行这部分代码时,都把count - 1,记做index
- 如果index为0,执行第4步,代表CyclicBarrier屏障已经拦截了足够数量(count)的线程,线程可以接着往下执行了。不为0,说明当前线程还没有达到屏障CyclicBarrier拦截的数量,执行第6步
- 调用nextGeneration()方法,唤醒所有等待线程
- breakBarrier()确保一定能执行唤醒动作
- 调用Condition的await()方法,将当前线程放入等待队列,释放锁
- 一定执行的释放锁动作。
nextGeneration()的代码如下:
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
使用Condition的signalAll()方法,唤醒全部等待线程
说完CyclicBarrier的原理之后,再对本篇的使用示例做一下描述:
- 线程1开始执行,调用await()方法,获得锁。此时count为3,count--,故count为2,index为2,调用Condition.await()方法,线程1进入等待队列,释放锁
- 线程2开始执行,过程与第一步相同,只是count减为1
- 线程3开始执行,获得锁,count减为0,达到拦截数量,调用nextGeneration()方法唤醒全部线程,释放自己持有的锁
- 线程1,2都被唤醒,根据锁竞争结果,依次执行完await()方法,最后释放锁
- 3个线程再往下执行自己的run()方法
异常分析:
假设调用cyclicBarrier.await()进行等待的线程数大于屏障CyclicBarrier实例化时声明的拦截数,会发生什么情况呢?
例如如下代码:
public static void main(String[] args) {
//屏障,阻拦3个线程
CyclicBarrier cyclicBarrier = new CyclicBarrier();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1正在执行");
try {
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程1运行结束,时间: " + System.currentTimeMillis());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2正在执行");
try {
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程2运行结束,时间: " + System.currentTimeMillis());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3正在执行");
try {
//线程3阻塞2秒,测试效果
// Thread.sleep(2000);
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程3运行结束,时间: " + System.currentTimeMillis());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程4正在执行");
try {
// 等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程4运行结束,时间: " + System.currentTimeMillis());
}
}).start();
}
调用cyclicBarrier.await()方法等待的线程一共4个,CyclicBarrier声明只拦截3个。
上述用例将导致一个线程得不到执行,处于等待状态。
分析一下原因:
在CyclicBarrier的dowait()方法215行(JDK1.8)中,只有在index == 0,也就是CyclicBarrier拦截到了实例化时指明的线程数量时,才会调用Condition.signalAll()唤醒等待线程。所以在第4个线程进入此方法时,index减为-1,会调用Condition.await()开始等待。这样就没有线程能执行唤醒逻辑了,它将一直处于等待状态。
线程屏障CyclicBarrier实现原理的更多相关文章
- Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier
Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...
- Java并发(十三):并发工具类——同步屏障CyclicBarrier
先做总结 1.CyclicBarrier 是什么? CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点) ...
- Java线程:概念与原理
Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...
- 线程局部变量ThreadLocal的原理及使用范围_1
线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...
- Netty中ByteBuf的引用计数线程安全的实现原理
原文链接 Netty中ByteBuf的引用计数线程安全的实现原理 代码仓库地址 ByteBuf 实现了ReferenceCounted 接口,实现了引用计数接口,该接口的retain(int) 方法为 ...
- 基于C++11实现线程池的工作原理
目录 基于C++11实现线程池的工作原理. 简介 线程池的组成 1.线程池管理器 2.工作线程 3.任务接口, 4.任务队列 线程池工作的四种情况. 1.主程序当前没有任务要执行,线程池中的任务队列为 ...
- 【java】ThreadLocal线程变量的实现原理和使用场景
一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...
- 深入源码分析Java线程池的实现原理
程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对CPU利用的优化手段. 通过学习线程池原理,明白所 ...
- 21.线程池ThreadPoolExecutor实现原理
1. 为什么要使用线程池 在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题.因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处: 降低资源消耗 ...
随机推荐
- 批量下载文件php
做了个照片墙,要提供批量下载照片的功能,如果你会文件下载,那批量也是小菜一碟,就是把文件打包压缩为 zip 文件再下载,而php的内置类ZipArchive()让你很容易实现. 首先,配置php.i ...
- PC端无论页面有没有完全撑开把footer保持在最底部(不用定位)
最近在写项目,有的页面没有占到一屏,然后footer也就是底部就靠上了,这样很影响美观,于是在网上找了找,下面是我的成果 解决该问题的最好方法是采用CSS3提供的一种先进布局模型 :flexbox,可 ...
- CF643E Bear and Destroying Subtrees
题解 我们可以先写出\(dp\)式来. 设\(dp[u][i]\)表示以\(u\)为根的子树深度不超过\(i-1\)的概率 \(dp[u][i]=\prod (dp[v][i-1]+1)*\frac{ ...
- [POJ1637]Sightseeing tour:混合图欧拉回路
分析 混合图欧拉回路问题. 一个有向图有欧拉回路当且仅当图连通并且对于每个点,入度\(=\)出度. 入度和出度相等可以联想到(我也不知道是怎么联想到的)网络流除了源汇点均满足入流\(=\)出流.于是可 ...
- [CSP-S模拟测试]:小P的2048(模拟)
题目描述 最近,小$P$迷上了一款叫做$2048$的游戏.这块游戏在一个$n\times n$的棋盘中进行,棋盘的每个格子中可能有一个形如$2^k(k\in N^*)$的数,也可能是空的.游戏规则介绍 ...
- Python List 列表list()方法
Python基础数据类型之一列表list,在python中作用很强在,列表List可以包含不同类型的数据对像,同时它是一个有序的变量集合,每个变量可以存储一个地址.所有序列能用到的标准操作方法,列表也 ...
- 前端开发学习笔记 - 1. Node.JS安装笔记
Node.JS安装笔记 Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an ...
- 移动端续讲及zepto移动端插件外加touch插件介绍
媒体查询:针对不同设备,显示不同的样式. 设备像素比:dpr device-piexl-ratio 在he开发中,要一个3陪高清图片: 1080>=320*3 (主要是为了解决图片的失真问题) ...
- Linux内核调试方法总结之Kprobes
Kprobes [用途][参考kernel/Documentation/kprobes.txt帮助文档] Kprobes是一个轻量级内核调试工具,同时又是其他一些更高级的内核调试工具(如perf和sy ...
- iOS即时通讯之CocoaAsyncSocket源码解析四
原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...