简介

jdk原文

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other.
The barrier is called cyclic because it can be re-used after the waiting threads are released.

这句话翻译意思:CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier很有帮助。
这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的

抓住重点:1、允许一组线程相互等待直到达到一个公共屏障点,2、可以重复使用

简单举例就是:玩王者荣耀只有所有人进入游戏之前都必须加载到100%,所有人才能进入游戏。
与CountDownLatch比较

 
image.png

源码解析

先从构造方法入手

  /**
* Creates a new {@code CyclicBarrier} that will trip when the
* given number of parties (threads) are waiting upon it, and
* does not perform a predefined action when the barrier is tripped.
*
* @param parties the number of threads that must invoke {@link #await}
* before the barrier is tripped
* @throws IllegalArgumentException if {@code parties} is less than 1
*/
public CyclicBarrier(int parties) {
this(parties, null);
}
/**
* Creates a new {@code CyclicBarrier} that will trip when the
* given number of parties (threads) are waiting upon it, and which
* will execute the given barrier action when the barrier is tripped,
* performed by the last thread entering the barrier.
*
* @param parties the number of threads that must invoke {@link #await}
* before the barrier is tripped
* @param barrierAction the command to execute when the barrier is
* tripped, or {@code null} if there is no action
* @throws IllegalArgumentException if {@code parties} is less than 1
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}

从jdk注释我们可以看出:
第一个构造器:创建一个新的{@code CyclicBarrier},它会在
给定数量的屏障(线程)正在等待它,并且在屏障被触发时不执行预定义的操作。
第二个构造器:创建一个新的{@code CyclicBarrier},它会在
给定数量的屏障(线程)正在等待它,以及当屏障被触发时,优先执行barrierAction,方便处理更复杂的业务场景。


await()方法
调用await方法的线程告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。直到parties个参与线程调用了await方法,CyclicBarrier同样提供带超时时间的await和不带超时时间的await方法:
await()方法里面最主要就是doawait()

private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// 获取独占锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 当前先从
final Generation g = generation;
// 如果这个线程损坏了,抛出异常
if (g.broken)
throw new BrokenBarrierException(); // 如果线程中断了,抛出异常
if (Thread.interrupted()) {
// 将损坏状态设置为true
// 并通知其他阻塞在此屏障上的线程
breakBarrier();
throw new InterruptedException();
} // 获取下标
int index = --count;
// 如果是 0,说明最后一个线程调用了该方法
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 执行屏障任务
if (command != null)
command.run();
ranAction = true;
// 更新一代,将count重置,将generation重置
// 唤醒之前等待的线程
nextGeneration();
return 0;
} finally {
// 如果执行屏障任务的时候失败了,就将损坏状态设置为true
if (!ranAction)
breakBarrier();
}
} // loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
// 如果没有时间限制,则直接等待,直到被唤醒
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();
}
} // 当有任何一个线程中断了,就会调用breakBarrier方法
// 就会唤醒其他的线程,其他线程醒来后,也要抛出异常
if (g.broken)
throw new BrokenBarrierException(); // g != generation表示正常换代了,返回当前线程所在屏障的下标
// 如果 g == generation,说明还没有换代,那为什么会醒了?
// 因为一个线程可以使用多个屏障,当别的屏障唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
// 正是因为这个原因,才需要generation来保证正确。
if (g != generation)
return index; // 如果有时间限制,且时间小于等于0,销毁屏障并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 释放独占锁
lock.unlock();
}
}

总结如果该线程不是最后一个调用await方法的线程,则它会一直处于等待状态,除非发生以下情况:
最后一个线程到达,即index == 0
某个参与线程等待超时
某个参与线程被中断
调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态

Generation描述着CyclicBarrier的更新换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier之后,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。

默认barrier(屏障)是没有损坏的。当barrier(屏障)损坏了或者有一个线程中断了,则通过breakBarrier()来终止所有的线程:

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

breakBarrier()不仅会把broken设置为true,还会将所有处于等待状态的线程全部唤醒(singalAll)方法

注意CyclicBarrier使用独占锁来执行await方法,并发性可能不是很高

简单例子加深印象

/**
* @author shuliangzhao
* @Title: CyclicBarrierTest
* @ProjectName design-parent
* @Description: TODO
* @date 2019/6/3 0:23
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N); for(int i=0;i<N;i++) {
new Writer(barrier).start();
} /* try {
Thread.sleep(25000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("CyclicBarrier重用"); for(int i=0;i<N;i++) {
new Writer(barrier).start();
}*/
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
} @Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
try {
Thread.sleep(2000); //以睡眠来模拟写入数据操作
System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");
}
}
}

运行结果

image.png

怎么用多线程求和


/**
* @author shuliangzhao
* @Title: CyclicBarrier
* @ProjectName design-parent
* @Description: TODO
* @date 2019/6/3 0:18
*/
public class CyclicBarrierExc { //private static final Logger LOGGER = LoggerFactory.getLogger(CyclicBarrierExc.class); public static void main(String[] args) {
//数组大小
int size = 50000;
//定义数组
int[] numbers = new int[size]; //随机初始化数组
for (int i = 0; i < size; i++) {
numbers[i] = RandomUtils.nextInt(100, 1000);
} //多线程计算结果
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//定义五个Future去保存子数组计算结果
final int[] results = new int[5]; //定义一个循环屏障,在屏障线程中进行计算结果合并
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
int sums = 0;
for (int i = 0; i < 5; i++) {
sums += results[i];
}
System.out.println("多线程计算结果:" + sums);
}); //子数组长度
int length = 10000;
//定义五个线程去计算
for (int i = 0; i < 5; i++) {
//定义子数组
int[] subNumbers = Arrays.copyOfRange(numbers, (i * length), ((i + 1) * length));
//盛放计算结果
int finalI = i;
executorService.submit(() -> {
for (int j = 0; j < subNumbers.length; j++) {
results[finalI] += subNumbers[j];
}
//等待其他线程进行计算
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
} //关闭线程池
executorService.shutdown();
}
}

Java并发编程系列之CyclicBarrier详解的更多相关文章

  1. Java并发编程系列之Semaphore详解

    简单介绍 我们以饭店为例,假设饭店只有三个座位,一开始三个座位都是空的.这时如果同时来了三个客人,服务员人允许他们进去用餐,然后对外说暂无座位.后来的客人必须在门口等待,直到有客人离开.这时,如果有一 ...

  2. Java并发编程1--synchronized关键字用法详解

    1.synchronized的作用 首先synchronized可以修饰方法或代码块,可以保证同一时刻只有一个线程可以执行这个方法或代码块,从而达到同步的效果,同时可以保证共享变量的内存可见性 2.s ...

  3. Java并发编程系列-(2) 线程的并发工具类

    2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...

  4. 【原创】Java并发编程系列1:大纲

    [原创]Java并发编程系列1:大纲 一个人能力当中所蕴藏的潜能,远超过自己想象以外. 为什么要学习并发编程 随着现今互联网行业的迅猛发展,其业务复杂度.并发量也在不断增加,对程序的要求变得越来越高, ...

  5. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  6. 干货:Java并发编程系列之volatile(二)

    接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...

  7. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

  8. Java并发编程系列-(4) 显式锁与AQS

    4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...

  9. Java并发编程系列-(3) 原子操作与CAS

    3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...

随机推荐

  1. 建造高塔(codevs 1689)

    题目描述 Description n有n种石块,石块能无限供应.每种石块都是长方体,其中第i种石块的长.宽.高分别为li.wi.hi.石块可以旋转,使得其中两维成为长度和宽度,第三维成为高度.如果要把 ...

  2. ZOJ 4016 Mergeable Stack 链表

    Mergeable Stack Time Limit: 2 Seconds      Memory Limit: 65536 KB Given  initially empty stacks, the ...

  3. POJ 1769_Minimizing maximizer

    题意: 一系列m个1~n区间,每个区间固定对某个子区间进行排序,顺序选择若干区间,使最终覆盖所有区间. 分析: computes the length of the shortest subseque ...

  4. UVA 10603_Fill

    题意: 给定三个杯子容量,初始只有第三个杯子满,其余为空,求最少需要倒多少水才能让某一杯子中有d升水,如果不能达到,则小于d且尽量接近. 分析: 因为要求转移水量最少,所以采用优先级队列保存每次的状态 ...

  5. 洛谷—— P1690 贪婪的Copy

    https://www.luogu.org/problem/show?pid=1690 题目描述 Copy从卢牛那里听说在一片叫yz的神的领域埋藏着不少宝藏,于是Copy来到了这个被划分为个区域的神地 ...

  6. 使用HttpClient调用第三方接口

    最近项目中需要调用第三方的Http接口,这里我用到了HttpClient. 首先我们要搞明白第三方接口中需要我们传递哪些参数.数据,搞明白参数以后我们就可以使用HttpClient调用接口了. 1.调 ...

  7. Spring Boot实现多个数据源教程收集(待实践)

    先收集,后续实践. http://blog.csdn.net/catoop/article/details/50575038 http://blog.csdn.net/neosmith/article ...

  8. JAVA 小程序之ATM

    一个JAVA的小程序,主要要求有模块化编程的思想,能够把ATM中各个功能独立成为一个一个的方法. ATM主要功能有: 查询余额: 取款: 存款: 修改密码: 退出. 以上功能均由独立的方法给出,具体实 ...

  9. 006 Cisco switch prewired

    Switch>en Switch#config t Enter configuration commands, one per line.  End with CNTL/Z. Switch(co ...

  10. [Unity3D]Unity3D游戏开发之从Unity3D到Eclipse

    ---------------------------------------------------------------------------------------------------- ...