生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具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. 软件安装——internal error2503/2502

    安装新的软件后先报internal error 2503,随后报internal error 2502.就是不让我装新的软件,提示说发生严重错误,然后安装失败. Solution for intern ...

  2. time,sys,os模块

      1.time模块 a:结构化时间:struct_time:通过time.localtime获取到一个时间对象,通过这个对象得到对象属性 ****localtime()如果没有参数,默认返回是一个时 ...

  3. CodeForces - 474D (dp)

    题目:https://vjudge.net/contest/326867#problem/B 题意:有很多个蛋糕,现在你有两种吃蛋糕的吃法,一次吃一个,定为A,一次吃k个定为B,然后问你吃m个蛋糕有多 ...

  4. 小程序swiper实现订单页面

    小程序swiper实现订单页面 myOrder.wxml <!--pages/myorder/myorder.wxml--> <view class="swiper-tab ...

  5. Linux shell】grep命令精确匹配字符串查找

    需求: 精确匹配查找某个字符串   精确匹配: 例如: 在抽取字符串“48”,返回结果包含诸如484和483等包含“48”的其他字符串,实际上应精确抽取只包含48的各行. 使用grep抽取精确匹配的一 ...

  6. linux让命令或程序在终端后台运行的方法(Ubuntu/Fedora/Centos等一样适用)

    https://segmentfault.com/a/1190000008314935

  7. 5个用/不用GraphQL的理由

    我在如何使用Gatsby建立博客 / How to build a blog with Gatsby这篇文章中提过GraphQL在Gatsby中的应用.总的来讲,它是一个新潮的技术,在适宜的使用场景威 ...

  8. EXISTS 和 IN 的查询效率问题

    mysql使用总结: 一:EXISTS 和 IN 的查询效率问题 1:当a表的数据小于b表中的数据时 用 IN 的效率是要小于用 EXISTS 的效率 SELECT * FROM a WHERE id ...

  9. Maven中Jar包冲突,不让某个Jar包打入到工程中

     查看工程目前冲突的Jar包,这里以fastjson.jar为例: 打开工程的pom.xml,进入到Dependency Hierarchy页面搜寻目标Jar. 从这里我们可以看到目前工程中有2处导入 ...

  10. Delphi XE2 之 FireMonkey 入门(19) - TFmxObject 的子类们(表)

    参考: 和 FMX 相关的类(表) TFmxObject IFreeNotification             TAnimation TBitmapAnimation           TBi ...