线程屏障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. 为什么要使用线程池 在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题.因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处: 降低资源消耗 ...
随机推荐
- 跳转控制语句continue
1 continue的使用场景: 1.1 在循环语句中 注意:离开使用场景的存在是没有意义的 2 continue的作用: 2.1 单层循环对比break,然后总结两者的区别 2.1.1 break ...
- 【CF1252J】Tiling Terrace(DP)
题意:有一个长为n的串,每个字符是#或者.中的一个,#不超过50个 有3种覆盖串的方式:(.),(..),(.#.),分别能获得g1,g2,g3的收益,覆盖之间不能重叠 第一种方式不能使用超过K次,问 ...
- Java 静态初始化块等的执行顺序
实例代码 package text; class Root { static{ System.out.println("Root的静态初始化块"); } { System.out. ...
- [design pattern](1) Strategy
引言 最近,在学习设计模式相关的知识.本博客主要想讲一讲策略模式,这也是我学习的第一个模式.写下这篇博客,主要想记录下个人的一点理解,也是想通过写博客的方式来加深对与Strategy的一点理解.以下的 ...
- [codeforces743C]:Vladik and fractions(数学)
题目传送门 题目描述 请找出一组合法解使得$\frac{1}{x}+\frac{1}{y}+\frac{1}{z}=\frac{2}{n}$成立. 其中$x,y,z$为正整数且互不相同. 输入格式 一 ...
- window下启动redis服务
---恢复内容开始--- 在windows环境下启动redis服务,前提是你安装好了,启动如下: 一,进入redis的安装目录下,在地址栏输入“cmd”,回车 二,然后会进入cmd界面,直接运行命令r ...
- vue2.0 之 douban (四)创建Swipe图片轮播组件
swiper中文文档:http://www.swiper.com.cn 1.我们在components文件夹里创建一个swipe组件,将需要用到的js以及css文件复制到assets/lib文件夹下, ...
- Helvetic Coding Contest 2019 online mirror (teams allowed, unrated)
http://codeforces.com/contest/1184 A1 找一对整数,使x^x+2xy+x+1=r 变换成一个分式,保证整除 #include<iostream> #in ...
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_5_BufferedWriter_字符缓冲输出流
使用newLine来换行 同样的效果 println的源码里面其实就用的就是newLine()
- Week13 - 376. Wiggle Subsequence
Week13 - 376. Wiggle Subsequence A sequence of numbers is called a wiggle sequence if the difference ...