生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具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
}
}
  1. 对当前对象加锁
  2. 每个线程获得锁,执行这部分代码时,都把count - 1,记做index
  3. 如果index为0,执行第4步,代表CyclicBarrier屏障已经拦截了足够数量(count)的线程,线程可以接着往下执行了。不为0,说明当前线程还没有达到屏障CyclicBarrier拦截的数量,执行第6步
  4. 调用nextGeneration()方法,唤醒所有等待线程
  5. breakBarrier()确保一定能执行唤醒动作
  6. 调用Condition的await()方法,将当前线程放入等待队列,释放锁
  7. 一定执行的释放锁动作。

  nextGeneration()的代码如下:

private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}

  使用Condition的signalAll()方法,唤醒全部等待线程

  说完CyclicBarrier的原理之后,再对本篇的使用示例做一下描述:

  1. 线程1开始执行,调用await()方法,获得锁。此时count为3,count--,故count为2,index为2,调用Condition.await()方法,线程1进入等待队列,释放锁
  2. 线程2开始执行,过程与第一步相同,只是count减为1
  3. 线程3开始执行,获得锁,count减为0,达到拦截数量,调用nextGeneration()方法唤醒全部线程,释放自己持有的锁
  4. 线程1,2都被唤醒,根据锁竞争结果,依次执行完await()方法,最后释放锁
  5. 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实现原理的更多相关文章

  1. Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...

  2. Java并发(十三):并发工具类——同步屏障CyclicBarrier

    先做总结 1.CyclicBarrier 是什么? CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点) ...

  3. Java线程:概念与原理

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  4. 线程局部变量ThreadLocal的原理及使用范围_1

    线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...

  5. Netty中ByteBuf的引用计数线程安全的实现原理

    原文链接 Netty中ByteBuf的引用计数线程安全的实现原理 代码仓库地址 ByteBuf 实现了ReferenceCounted 接口,实现了引用计数接口,该接口的retain(int) 方法为 ...

  6. 基于C++11实现线程池的工作原理

    目录 基于C++11实现线程池的工作原理. 简介 线程池的组成 1.线程池管理器 2.工作线程 3.任务接口, 4.任务队列 线程池工作的四种情况. 1.主程序当前没有任务要执行,线程池中的任务队列为 ...

  7. 【java】ThreadLocal线程变量的实现原理和使用场景

    一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...

  8. 深入源码分析Java线程池的实现原理

    程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对CPU利用的优化手段. 通过学习线程池原理,明白所 ...

  9. 21.线程池ThreadPoolExecutor实现原理

    1. 为什么要使用线程池 在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题.因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处: 降低资源消耗 ...

随机推荐

  1. Navicat 出现的[Err] 1146 - Table 'performance_schema.session_status' doesn't exist已解决

    [Err] 1146 - Table 'performance_schema.session_status' doesn't exist已解决   刚刚接触MySQL,就往数据库添加数据,就遇到这个问 ...

  2. Java——容器(Auto-boxing/unboxing)

    [打包/解包] 在Map中需要增加一个数值时,需要new一个对象出来,输出后又得进行强制类型转换,这就造成不便.在JDK1.5中使用Map接口提供了一种新的机制.   在合适的时机自动打包/解包(在J ...

  3. [CF342C]Cupboard and Balloons 题解

    前言 博主太弱了 题解 这道题目是一个简单的贪心. 首先毋庸置疑,柜子的下半部分是要放满的. 于是我们很容易想到,分以下三种情况考虑: \[\small\text{请不要盗图,如需使用联系博主}\] ...

  4. 贪心整理&一本通1431:钓鱼题解

    题目传送 (其实有一个更正经的题解) 看了许久,发现这题貌似就是一个动态规划啊,但毕竟是贪心题库里的题,还是想想用贪心解吧. 经过(借鉴大佬思路)十分复杂的思考后,终于理解出了这题的贪心思路.该题的难 ...

  5. oracle触发器update本表数据

    功能: 1. 允许/限制对表的修改 2. 自动生成派生列,比如自增字段 3. 强制数据一致性 4. 提供审计和日志记录 5. 防止无效的事务处理 6. 启用复杂的业务逻辑 开始 create trig ...

  6. 如何删除由Automater创建的服务

    想要设置两个实用的快捷设置(如何设置): 1.复制当前文件或者文件夹路径 2.在终端打开文件夹 然后想到可以用mac自带的自动操作这款软件,英文叫Automater.接着发现,显示路径栏后,直接就提供 ...

  7. Python分析《武林外传》 -----转载

    转载原链接:http://www.cnblogs.com/fredkeke/p/8328387.html 我一向比较喜欢看武侠电影.小说,但是06年武林外传开播的时候并没有追剧,简单扫几眼之后发现他们 ...

  8. 字符串中的TRIM操作

    std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") ...

  9. shell脚本中执行python脚本并接收其返回值的例子

    1.在shell脚本执行python脚本时,需要通过python脚本的返回值来判断后面程序要执行的命令 例:有两个py程序  hello.py 复制代码代码如下: def main():    pri ...

  10. oracle--groupby分组学习

    使用group by分组 在多行函数中不能直接使用普通字段,除非group by 在多行函数中不能直接使用单行函数,除非group by group by学习: ---1.使用group by进行数据 ...