前言

  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. 在word文档里提取出所有的邮箱地址

    怎样在word文档里提取出所有的邮箱地址 文档内容太多,邮箱也有很多,一个个复制粘贴太浪费时间,怎样把这些邮箱简单的提取出来  答案:用查找功能. 查找目标:[A-z,0-9]{1,}\@[A-z,0 ...

  2. cocoapods 安装过程及常见问题

    1.可以参考这个网页的教程:http://code4app.com/article/cocoapods-install-usage 2.按照以下步骤进行安装: 1.配置rugy静态环境 gem sou ...

  3. UVA 10815:Andy's First Dictionary(STL)

    题意:给出一段英文,里面包含一些单词,空格和标点,单词不区分大小写,默认都为小写.按照字典序输出这些单词(这些单词不能有重复,字母全部变成小写) stringstream:包含在头文件#include ...

  4. Java8 (Function,Consumer,Predicate,Supplier)详解

    1. https://blog.csdn.net/lzm18064126848/article/details/70199769 1.1 https://blog.csdn.net/turbo_zon ...

  5. WIFI_认证加密学习_STA_AP_WDS

    2-1.1_15_使用卡1_准备工作及配置内核====================================1.无线网卡连接上路由或AP之后使用上是和有线网卡是一样的,都是socket编程. ...

  6. flash流媒体资料

    http://www.docin.com/p-111227070.html Java mina和c++ ace做socket长连接 测试报告: http://www.iteye.com/problem ...

  7. 使用gitblit搭建一个简单的局域网服务器

    使用gitblit搭建一个简单的局域网服务器 1.使用背景 现在很多使用github管理代码,但是github需要互联网的支持,而且私有的git库需要收费.有一些项目的代码不能外泄,所以,搭建一个局域 ...

  8. npm bower gulp

    npm即nodejs package manager ,是nodeJs的包管理工具,使用前需安装nodeJs(https://www.npmjs.com.cn/getting-started/inst ...

  9. 基于TLS(线程局部存储)的高效timelog实现

    什么是timelog? 我们在分析程序性能的时候,会加入的一些logging信息记录每一部分的时间信息 timelog模块的功能就是提供统一的接口来允许添加和保存logging 我们正在用的timel ...

  10. Git Bash主题配置

    考虑到window的 Vim操作,发现Git Bash自带命令行很好用. Vim写作Markdown真的好用 还不是为了装逼 只是配色很不爽,就找了这个. 不要怕非常简单麻烦,需要2步骤 1- 打开g ...