生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具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. k8s登录harbor报错:Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request cance

    [root@k8s-node02 ~]# docker login 192.168.180.105:1180 Username: admin Password: Error response from ...

  2. 特征提取算法(2)——HOG特征提取算法

    histogram of oriented gradient(方向梯度直方图)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和统计图像局部区域的梯度方向直方图来构成特征.H ...

  3. [CSP-S模拟测试]:小P的2048(模拟)

    题目描述 最近,小$P$迷上了一款叫做$2048$的游戏.这块游戏在一个$n\times n$的棋盘中进行,棋盘的每个格子中可能有一个形如$2^k(k\in N^*)$的数,也可能是空的.游戏规则介绍 ...

  4. Longest Subarray(HDU6602+线段树)

    题意 要你找一个最长的区间使得区间内每一个数出现次数都大于等于K. 题解->https://blog.csdn.net/Ratina/article/details/97503663 #incl ...

  5. React Native商城项目实战14 - 首页中间下部分

    1.MiddleBottomView.js /** * 首页中间下部分 */ import React, { Component } from 'react'; import { AppRegistr ...

  6. React-Native 之 GD (十三)数据持久化(realm) 及 公共Cell

    1.数据持久化 数据持久化是移动端的一个重要部分,刚发现 Realm 原来已经支持 React-Native 了 步骤一: 引入 realm $ npm install realm --save 步骤 ...

  7. leetcode-mid-Linked list- 230 Kth Smallest Element in a BST

    mycode  81.40% # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x ...

  8. mysql5.6.36 源码安装过程

    参考:http://www.linuxidc.com/Linux/2015-06/119354.htm cmake编译时,提示错误 CMake Error: The source directory ...

  9. dp培训完结(8.9)

    概率与期望dp 期望: 为什么下面的式子成立? 若x可以取1,2,3,则x+c可以取1+c,2+c,3+c..........x*c可以取1*c,2*c,3*c why? 举个例子(E(x+y)=E( ...

  10. Powershell 邮件发送

    目录 目录 前言 Send-MailMessage NETMail 使用OutLook发送邮件 前言 最近领导想在winServer2012上搞个自动发送邮件的计划任务,下面有几种发送邮件的方式. 如 ...