Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

CyclicBarrier简介

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。

注意比较CountDownLatchCyclicBarrier
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

CyclicBarrier函数列表

CyclicBarrier(int parties)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。 int await()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
int await(long timeout, TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
int getNumberWaiting()
返回当前在屏障处等待的参与者数目。
int getParties()
返回要求启动此 barrier 的参与者数目。
boolean isBroken()
查询此屏障是否处于损坏状态。
void reset()
将屏障重置为其初始状态。

CyclicBarrier数据结构

CyclicBarrier的UML类图如下:

CyclicBarrier是包含了"ReentrantLock对象lock"和"Condition对象trip",它是通过独占锁实现的。下面通过源码去分析到底是如何实现的。

CyclicBarrier的使用示例

示例1
新建5个线程,这5个线程达到一定的条件时,它们才继续往后运行。

 1 import java.util.concurrent.CyclicBarrier;
2 import java.util.concurrent.BrokenBarrierException;
3
4 public class CyclicBarrierTest1 {
5
6 private static int SIZE = 5;
7 private static CyclicBarrier cb;
8 public static void main(String[] args) {
9
10 cb = new CyclicBarrier(SIZE);
11
12 // 新建5个任务
13 for(int i=0; i<SIZE; i++)
14 new InnerThread().start();
15 }
16
17 static class InnerThread extends Thread{
18 public void run() {
19 try {
20 System.out.println(Thread.currentThread().getName() + " wait for CyclicBarrier.");
21
22 // 将cb的参与者数量加1
23 cb.await();
24
25 // cb的参与者数量等于5时,才继续往后执行
26 System.out.println(Thread.currentThread().getName() + " continued.");
27 } catch (BrokenBarrierException e) {
28 e.printStackTrace();
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 }
33 }
34 }

运行结果

Thread-1 wait for CyclicBarrier.
Thread-2 wait for CyclicBarrier.
Thread-3 wait for CyclicBarrier.
Thread-4 wait for CyclicBarrier.
Thread-0 wait for CyclicBarrier.
Thread-0 continued.
Thread-4 continued.
Thread-2 continued.
Thread-3 continued.
Thread-1 continued.

结果说明:主线程中新建了5个线程,所有的这些线程都调用cb.await()等待。所有这些线程一直等待,直到cb中所有线程都达到barrier时,这些线程才继续运行!

示例2

新建5个线程,当这5个线程达到一定的条件时,执行某项任务。

 1 import java.util.concurrent.CyclicBarrier;
2 import java.util.concurrent.BrokenBarrierException;
3
4 public class CyclicBarrierTest2 {
5
6 private static int SIZE = 5;
7 private static CyclicBarrier cb;
8 public static void main(String[] args) {
9
10 cb = new CyclicBarrier(SIZE, new Runnable () {
11 public void run() {
12 System.out.println("CyclicBarrier's parties is: "+ cb.getParties());
13 }
14 });
15
16 // 新建5个任务
17 for(int i=0; i<SIZE; i++)
18 new InnerThread().start();
19 }
20
21 static class InnerThread extends Thread{
22 public void run() {
23 try {
24 System.out.println(Thread.currentThread().getName() + " wait for CyclicBarrier.");
25
26 // 将cb的参与者数量加1
27 cb.await();
28
29 // cb的参与者数量等于5时,才继续往后执行
30 System.out.println(Thread.currentThread().getName() + " continued.");
31 } catch (BrokenBarrierException e) {
32 e.printStackTrace();
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 }
37 }
38 }

运行结果

Thread-1 wait for CyclicBarrier.
Thread-2 wait for CyclicBarrier.
Thread-3 wait for CyclicBarrier.
Thread-4 wait for CyclicBarrier.
Thread-0 wait for CyclicBarrier.
CyclicBarrier's parties is: 5
Thread-0 continued.
Thread-4 continued.
Thread-2 continued.
Thread-3 continued.
Thread-1 continued.

结果说明:主线程中新建了5个线程,所有的这些线程都调用cb.await()等待。所有这些线程一直等待,直到cb中所有线程都达到barrier时,执行新建cb时注册的Runnable任务。

CyclicBarrier源码分析(基于JDK1.7.0_40)

1. 构造函数

CyclicBarrier的构造函数共2个:CyclicBarrier 和 CyclicBarrier(int parties, Runnable barrierAction)。第1个构造函数是调用第2个构造函数来实现的,下面第2个构造函数的源码。

public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// parties表示“必须同时到达barrier的线程个数”。
this.parties = parties;
// count表示“处在等待状态的线程个数”。
this.count = parties;
// barrierCommand表示“parties个线程到达barrier时,会执行的动作”。
this.barrierCommand = barrierAction;
}

CyclicBarrier(int parties):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

2. 等待函数

CyclicBarrier.java中await()方法如下:

public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}

说明:await()是通过dowait()实现的。

private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 获取“独占锁(lock)”
lock.lock();
try {
// 保存“当前的generation”
final Generation g = generation; // 若“当前generation已损坏”,则抛出异常。
if (g.broken)
throw new BrokenBarrierException(); // 如果当前线程被中断,则通过breakBarrier()终止CyclicBarrier,唤醒CyclicBarrier中所有等待线程。
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
} // 将“count计数器”-1
int index = --count;
// 如果index=0,则意味着“有parties个线程到达barrier”。
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 如果barrierCommand不为null,则执行该动作。
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 唤醒所有等待线程,并更新generation。
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
} // 当前线程一直阻塞,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生,
// 当前线程才继续执行。
for (;;) {
try {
// 如果不是“超时等待”,则调用awati()进行等待;否则,调用awaitNanos()进行等待。
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 如果等待过程中,线程被中断,则执行下面的函数。
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
} // 如果“当前generation已经损坏”,则抛出异常。
if (g.broken)
throw new BrokenBarrierException(); // 如果“generation已经换代”,则返回index。
if (g != generation)
return index; // 如果是“超时等待”,并且时间已到,则通过breakBarrier()终止CyclicBarrier,唤醒CyclicBarrier中所有等待线程。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 释放“独占锁(lock)”
lock.unlock();
}
}

说明:dowait()的作用就是让当前线程阻塞,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生,当前线程才继续执行。
(01) generation是CyclicBarrier的一个成员遍历,它的定义如下:

private Generation generation = new Generation();

private static class Generation {
boolean broken = false;
}

在CyclicBarrier中,同一批的线程属于同一代,即同一个Generation;CyclicBarrier中通过generation对象,记录属于哪一代。
当有parties个线程到达barrier,generation就会被更新换代。

(02) 如果当前线程被中断,即Thread.interrupted()为true;则通过breakBarrier()终止CyclicBarrier。breakBarrier()的源码如下:

private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}

breakBarrier()会设置当前中断标记broken为true,意味着“将该Generation中断”;同时,设置count=parties,即重新初始化count;最后,通过signalAll()唤醒CyclicBarrier上所有的等待线程。

(03) 将“count计数器”-1,即--count;然后判断是不是“有parties个线程到达barrier”,即index是不是为0。
当index=0时,如果barrierCommand不为null,则执行该barrierCommand,barrierCommand就是我们创建CyclicBarrier时,传入的Runnable对象。然后,调用nextGeneration()进行换代工作,nextGeneration()的源码如下:

private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}

首先,它会调用signalAll()唤醒CyclicBarrier上所有的等待线程;接着,重新初始化count;最后,更新generation的值。

(04) 在for(;;)循环中。timed是用来表示当前是不是“超时等待”线程。如果不是,则通过trip.await()进行等待;否则,调用awaitNanos()进行超时等待。

 

Java - "JUC" CyclicBarrier源码分析的更多相关文章

  1. Java - "JUC" CountDownLatch源码分析

    Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...

  2. Java - "JUC" Semaphore源码分析

    Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了 ...

  3. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  4. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  5. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  6. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

  7. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  8. 并发工具CyclicBarrier源码分析及应用

      本文首发于微信公众号[猿灯塔],转载引用请说明出处 今天呢!灯塔君跟大家讲: 并发工具CyclicBarrier源码分析及应用 一.CyclicBarrier简介 1.简介 CyclicBarri ...

  9. concurrent(六)同步辅助器CyclicBarrier & 源码分析

    参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...

随机推荐

  1. 【javascript】原生js更改css样式的两种方式

    下面我给大家介绍的是原生js更改CSS样式的两种方式: 1通过在javascript代码中的node.style.cssText="css表达式1:css表达式2:css表达式3  &quo ...

  2. Python-实现图表绘制总结

    Numpy是Python开源的数值计算扩展,可用来存储和处理大型矩阵,比Python自身数据结构要高效: matplotlib是一个Python的图像框架,使用其绘制出来的图形效果和MATLAB下绘制 ...

  3. [学习笔记]Splay

    其实就是一道题占坑啦 [NOI2005]维护数列 分析: 每次操作都要 \(Splay\) 一下 \(Insert\) 操作:重建一棵平衡树,把 \(l\) 变成根,\(l+2\) 变成右子树的根,那 ...

  4. [HTML] SCSS 备忘录

    Sass是成熟.稳定.强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能. 特性概览 CSS书写代码规模较大的Web应用时,容易造 ...

  5. 5_Python OOP

    1. 实例属性和类属性        (1) 实例属性在构造函数__init__中定义,定义时以self作为前缀,只能通过实例名访问        (2) 类属性在类中方法之外单独定义,还可以在程序中 ...

  6. linux安装mysql数据库和简单配置

  7. 阿里巴巴Java开发规范---个人总结

    一.编程规约 (一) 命名规约 1. [强制]所有编程相关命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束. 反例: _name / __name / $Object / name_ / ...

  8. Selenium之元素定位

    1.查看页面元素:ID.class.type.name等. 2.通过webdriver的方法定位: find_element_by_name()  find_element_by_id()  find ...

  9. Asp.Net Core 集成 Hangfire 配置使用 Redis 存储

    Hangfire 官方支持 MSSQL 与 Redis(Hangfire.Pro.Redis) 两种 ,由于我的数据库是 MYSQL ,粗略查询了一下文档,现在对 .NET Core 支持的并不够好, ...

  10. Linux下升级Python到3.5.2版本

    原文出处:https://www.cnblogs.com/tssc/p/7762998.html 本文主要介绍在Linux(CentOS)下将Python的版本升级为3.5.2的方法 众所周知,在20 ...