前言

  JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;

  CountDownLatch、CyclicBarrier、Semphore、Phaser 这四个工具类提供一种并发流程的控制手段;而Exchanger工具类则提供了在线程之间交换数据的一种手段。

简介

  CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

构造方法摘要

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

方法摘要

方法名称 说明
public int await() throws
InterruptedException, BrokenBarrierException
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
返回:到达的当前线程的索引,其中,索引 getParties() - 1 指示将到达的第一个线程,零指示最后一个到达的线程.
public int await(long timeout,TimeUnit unit) throws
InterruptedException,BrokenBarrierException,
ITimeoutException
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
public void reset() 将屏障重置为其初始状态。如果所有参与者目前都在屏障处等待,则它们将返回,同时抛出一个 BrokenBarrierException。注意,在由于其他原因造成损坏之后,实行重置可能会变得很复杂;
public boolean isBroken() 查询此屏障是否处于损坏状态。
public int getNumberWaiting() 返回当前在屏障处等待的参与者数目。此方法主要用于调试和断言。
public int getParties() 返回要求启动此 barrier 的参与者数目。

注意:

  • 对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。
  • 内存一致性效果:线程中调用 await() 之前的操作 happen-before 那些是屏障操作的一部份的操作,后者依次 happen-before 紧跟在从另一个线程中对应 await() 成功返回的操作。

@ Example1 屏障操作的例子
public static void main(String[] args) {
//设置5个屏障,并且有屏障操作
CyclicBarrier barrier = new CyclicBarrier(5,new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"执行了屏障操作");
}
}); for(int i=0;i<5;i++){
//创建5个线程
Thread thread = new Thread(new MyRunable(barrier),"thread_"+i);
thread.start();
}
}
class MyRunable implements Runnable{

	CyclicBarrier barrier;
public MyRunable(CyclicBarrier barrier ){
this.barrier = barrier;
} @Override
public void run() {
//一系列操作...
System.out.println("线程 "+Thread.currentThread().getName()+" 到达了屏障点!");
try {
int index = barrier.await();
if(index== (barrier.getParties()-1)){
//第一个到达屏障点的线程,执行特殊操作....
System.out.println("所有线程到达屏障点,线程 "+Thread.currentThread().getName()+" 被唤醒!!此线程是第一个到达屏障点");
}else if(index == 0){//最后一个到达屏障点的线程
System.out.println("所有线程到达屏障点,线程 "+Thread.currentThread().getName()+" 被唤醒!!此线程是最后一个到达屏障点");
}else{
System.out.println("所有线程到达屏障点,线程 "+Thread.currentThread().getName()+" 被唤醒!!");
}
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}

运行结果:

线程 thread_1 到达了屏障点!

线程 thread_4 到达了屏障点!

线程 thread_3 到达了屏障点!

线程 thread_0 到达了屏障点!

线程 thread_2 到达了屏障点!

线程thread_3执行了屏障操作

所有线程到达屏障点,线程 thread_3 被唤醒!!此线程是最后一个到达屏障点

所有线程到达屏障点,线程 thread_0 被唤醒!!

所有线程到达屏障点,线程 thread_4 被唤醒!!

所有线程到达屏障点,线程 thread_1 被唤醒!!此线程是第一个到达屏障点

所有线程到达屏障点,线程 thread_2 被唤醒!!

  上面的例子,使用了传入屏障操作的Runable参数的构造方法,屏障操作是由最后一个到达屏障点的线程执行的,这是不可以改变的。然而,在实际使用中,可能会出现由第n个到达屏障点的线程执行特殊的操作(或者说 屏障操作),那么就可以使用 CyclicBarrier.await()进行判断,如上面的例子,第一个和最后一个到达屏障点的线程都执行特殊的操作。

   顺便说一下,可能会对本例子中前5个输出的顺序 有所疑惑:thread_3 通过awiat()方法返回的索引值,可知 thread_3 是最后一个到达屏障点的,但为什么输出的顺序却是第三个,而不是最后一个;在这就要真正理解CyclicBarrier,CyclicBarrier 本质上是一把锁,多个线程在使用CyclicBarrier 对象时,是需要先获取锁,即需要互斥访问,所以调用await( )方法不一定能够马上获取锁。上面的例子,是先打印输出,再去获取锁,所以输出顺序不是到达屏障点的顺序。


@ Example2 应用场景

   下面的例子是:CyclicBarrier用于多线程计算数据,最后合并计算结果的场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

public class BankWaterService implements Runnable {

	//创建4个屏障,处理完后执行当前类的run方法
private CyclicBarrier barrier = new CyclicBarrier(4,this); //假设只有4个sheet,所以只启动4个线程
private Executor excutor = Executors.newFixedThreadPool(4); //保存每个sheet计算出的结果
private ConcurrentHashMap< String, Integer> sheetBankWaterCount = new ConcurrentHashMap<>(); private void count(){
for(int i=0;i<4;i++){
excutor.execute(new Runnable() { @Override
public void run() {
//计算过程.....
//存储计算结果
sheetBankWaterCount.put(Thread.currentThread().getName(), 1);
try {
//计算完成,插入屏障
barrier.await();
//后续操作,将会使用到四个线程的运行结果....
System.out.println("线程"+Thread.currentThread().getName()+"运行结束,最终的计算结果:"+sheetBankWaterCount.get("result"));
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
} @Override
public void run() {
int result = 0;
for(Entry<String, Integer> item : sheetBankWaterCount.entrySet()){
result += item.getValue();
}
sheetBankWaterCount.put("result", result);
} public static void main(String[] args) {
BankWaterService bankWaterService = new BankWaterService();
bankWaterService.count();
}
}

运行结果:

线程pool-1-thread-4运行结束,最终的计算结果:4

线程pool-1-thread-2运行结束,最终的计算结果:4

线程pool-1-thread-1运行结束,最终的计算结果:4

线程pool-1-thread-3运行结束,最终的计算结果:4


CyclicBarrier和CountDownLatch的区别

  • CountDownLatch: 一个线程(或者多个线程), 等待另外N个线程完成某个事情之后才能执行。而这N个线程通过调用CountDownLatch.countDown()方法 来告知“某件事件”完成,即计数减一。而一个线程(或者多个线程)则通过CountDownLatch.awiat( ) 进入等待状态,直到 CountDownLatch的计数为0时,才会全部被唤醒
  • CyclicBarrier : N个线程相互等待,任何一个线程完成某个事情之前,所有的线程都必须等待。

    CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.

    而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.
  • CountDownLatch只能使用一次,CyclicBarrier则可以通过reset( )方法重置后,重新使用。所以CyclicBarrier可以用于更复杂的业务场景。例如:计算错误,可以重置计数器,并让线程重新执行一次。

文献:

并发工具类(二)同步屏障CyclicBarrier的更多相关文章

  1. Java并发工具类之同步屏障CyclicBarrier

    CyclicBarrier的字面意思是可以循环使用的Barrier,它要做的事情是让一个线程到达一个Barrier的时候被阻塞,直到最后一个线程到达Barrier,屏障才会放开,所有被Barrier拦 ...

  2. 并发工具类:CountDownLatch、CyclicBarrier、Semaphore

    在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch.CyclicBarrier.Semaphore. 一.CountDownLatch ...

  3. 【Java并发工具类】CountDownLatch和CyclicBarrier

    前言 下面介绍协调让多线程步调一致的两个工具类:CountDownLatch和CyclicBarrier. CountDownLatch和CyclicBarrier的用途介绍 CountDownLat ...

  4. Java中的并发工具类:CountDownLatch、CyclicBarrier和Semaphore

    在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法. 一. ...

  5. 并发工具类的使用 CountDownLatch,CyclicBarrier,Semaphore,Exchanger

    1.CountDownLatch 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助. A CountDownLatch用给定的计数初始化. await方法阻塞,直到由于countDo ...

  6. 多线程进阶之并发工具类:CountDownLatch、CyclicBarrier

    并发工具类在java.util.concurrent包下.常用的有CountDownLatch.CyclicBarrier,用它们可以控制并发流程. 1.CountDownLatch探究: 主要用到其 ...

  7. java 并发工具类CountDownLatch & CyclicBarrier

    一起在java1.5被引入的并发工具类还有CountDownLatch.CyclicBarrier.Semaphore.ConcurrentHashMap和BlockingQueue,它们都存在于ja ...

  8. Java中的并发工具类(CountDownLatch、CyclicBarrier、Semaphore、Exchanger)

    在JDK的并发包里提供了很多有意思的并发工具类.CountDownLatch.CyclicBarrier和Semaphore 工具类提供了一种并发流程控制的手段,Exchanger 工具类则提供了在线 ...

  9. java中的并发工具类

    在jdk的并发包里提供了几个非常有用的并发工具类.CountDownLatdch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线 ...

随机推荐

  1. STM32 输入捕获配置

    在STM32 的定时器,除了 TIM6 和 TIM7,就是通过检测 TIMx_CHx 上的 边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候, 将当时定时器 的值(TIMx_CNT) 存放到对 ...

  2. HDU 2009

    求数列的和 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submiss ...

  3. 放苹果问题 DP计数 m个苹果放在n个盘子里,苹果,盘子相同,盘子可为空

    详细的解释放苹果问题的链接:苹果可相同可不同,盘子可相同可不同,盘子可空和不可空,都有详细的说明··· http://www.cnblogs.com/celia01/archive/2012/02/1 ...

  4. jquery学习1之对juery对象的细节操作1

    jquery是前台动态页面开发的一个很重要的工具. 一:jquery对象中length属性和size()方法 var a=$("a").length;         var b= ...

  5. python 判断字符串中字符类型的常用方法

    s为字符串 s.isalnum() 所有字符都是数字或者字母 s.isalpha() 所有字符都是字母 s.isdigit() 所有字符都是数字 s.islower() 所有字符都是小写 s.isup ...

  6. C易忽视的基础

    1.输出格式控制:%x按int型16进制输出: %d按int型十进制输出:变量超出4字节会丢掉低位!!!!(却不是被截断!!!) void main() { int a=0x11223344; lon ...

  7. ASM 磁盘、目录的管理

    --======================== -- ASM 磁盘.目录的管理 --======================== ASM磁盘是ASM体系结构的重要组成部分,ASM磁盘由ASM ...

  8. JavaScript中类似PHP的uniqid()方法

    JavaScript中类似PHP的uniqid()方法: function generateUIDNotMoreThan1million() { return ("0000" + ...

  9. vue项目实现按需加载的3种方式:vue异步组件技术、es提案的import()、webpack提供的require.ensure()

    1. vue异步组件技术 vue-router配置路由,使用vue的异步组件技术,可以实现按需加载. 但是,这种情况下一个组件生成一个js文件. 举例如下: { path: '/promisedemo ...

  10. Apache关闭VirtualHost的Log日志记录

    有时我们的apache产生的日志是超大的并且 没什么用处,这时我们就可以关闭了,关闭apache日志很简单,直接ErrorLog off或 # CustomLog即可. Web server(ex: ...