CyclicBarrier有什么作用?

在现实生活中,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。

栅栏就可以很好的模拟这类场景,利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。下图演示了这一过程。

CyclicBarrier的实现原理

在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞此时计数器会减1当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便于下次使用,这其实就是实现一组线程相互等待的原理,

如果对awai()的调用超时,或者await()阻塞的线程被中断,那么栅栏就被认为是打破了。如果成功地通过栅栏,那么await()将为每个线程返回一个唯一的到达索引号。下面看看CyclicBarrier都有哪些成员变量

    //同步操作锁private final ReentrantLock lock = new ReentrantLock();//线程拦截器private final Condition trip = lock.newCondition();//每次拦截的线程数private final int parties;//换代前的任务private final Runnable barrierCommand;//栅栏当前代private Generation generation = new Generation();//计数器private int count;//静态内部类Generationprivate static class Generation {boolean broken = false;}

可以看到CyclicBarrier内部是通过条件队列trip来对线程进行阻塞的,并且其内部维护了两个int型的变量partiescountparties表示每次拦截的线程数,该值在构造时进行赋值

count是内部计数器,它的初始值和parties相同,以后随着每次await方法的调用而减1,直到减为0就将所有线程唤醒

CyclicBarrier有一个静态内部类Generation该类的对象代表栅栏的当前代,就像玩游戏时代表的本局游戏,利用它可以实现循环等待。

barrierCommand表示换代前执行的任务当count减为0时表示本局游戏结束,需要转到下一局。在转到下一局游戏之前会将所有阻塞的线程唤醒,在唤醒所有线程之前你可以通过指定barrierCommand来执行自己的任务

CyclicBarrier的构造函数

 public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;}public CyclicBarrier(int parties) {this(parties, null);
}

CyclicBarrier有两个构造器,其中构造器1是它的核心构造器,在这里你可以指定本局游戏的参与者数量(要拦截的线程数)以及本局结束时要执行的任务,还可以看到计数器count的初始值被设置为parties。CyclicBarrier类最主要的功能就是使先到达屏障点的线程阻塞并等待后面的线程,其中它提供了两种等待的方法,分别是定时等待非定时等待

//非定时等待
public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}}//定时等待
public int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException {return dowait(true, unit.toNanos(timeout));
}

可以看到不管是定时等待还是非定时等待,它们都调用了dowait方法,只不过是传入的参数不同而已。下面我们就来看看dowait方法都做了些什么。

//核心等待方法
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()) {//如果当前线程被中断会做以下三件事//1.打翻当前栅栏//2.唤醒拦截的所有线程//3.抛出中断异常breakBarrier();throw new InterruptedException();}//每次都将计数器的值减1int index = --count;//计数器的值减为0则需唤醒所有线程并转换到下一代if (index == 0) {boolean ranAction = false;try {//唤醒所有线程前先执行指定的任务final Runnable command = barrierCommand;if (command != null) {command.run();}ranAction = true;//唤醒所有线程并转到下一代nextGeneration();return 0;} finally {//确保在任务未成功执行时能将所有线程唤醒if (!ranAction) {breakBarrier();}}}//如果计数器不为0则执行此循环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();}}//如果线程因为打翻栅栏操作而被唤醒则抛出异常if (g.broken) {throw new BrokenBarrierException();}//如果线程因为换代操作而被唤醒则返回计数器的值if (g != generation) {return index;}//如果线程因为时间到了而被唤醒则打翻栅栏并抛出异常if (timed && nanos <= 0L) {breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();}
}

可以看到在dowait方法中每次都将count减1减完后立马进行判断看看是否等于0如果等于0的话就会先去执行之前指定好的任务,执行完之后再调用nextGeneration方法将栅栏转到下一代,在该方法中会将所有线程唤醒,将计数器的值重新设为parties,最后会重新设置栅栏代次,在执行完nextGeneration方法之后就意味着游戏进入下一局。

如果计数器此时还不等于0的话就进入for循环,根据参数来决定是调用trip.awaitNanos(nanos)还是trip.await()方法,这两方法对应着定时和非定时等待。如果在等待过程中当前线程被中断就会执行breakBarrier方法,该方法叫做打破栅栏,意味着游戏在中途被掐断,设置generation的broken状态为true并唤醒所有线程。同时这也说明在等待过程中有一个线程被中断整盘游戏就结束,所有之前被阻塞的线程都会被唤醒。

线程醒来后会执行下面三个判断,看看是否因为调用breakBarrier方法而被唤醒,如果是则抛出异常;看看是否是正常的换代操作而被唤醒,如果是则返回计数器的值;看看是否因为超时而被唤醒,如果是的话就调用breakBarrier打破栅栏并抛出异常。这里还需要注意的是,如果其中有一个线程因为等待超时而退出,那么整盘游戏也会结束,其他线程都会被唤醒。下面贴出nextGeneration方法和breakBarrier方法的具体代码。

//重置栅栏到下一代
private void nextGeneration() {// 唤醒条件队列中的所有线程trip.signalAll();// 设置计数器的值为需要拦截的线程数count = parties;//重新设置栅栏代次generation = new Generation();
}//中断当前栅栏
private void breakBarrier() {generation.broken = true;//设置计数器的值为需要拦截的线程数count = parties;//唤醒所有线程trip.signalAll();
}

CyclicBarrier和CountDownLatch的区别

这两个类都可以实现一组线程在到达某个条件之前进行等待,它们内部都有一个计数器,当计数器的值不断的减为0的时候所有阻塞的线程将会被唤醒。

栅栏(Barrier)类似于闭锁,他能阻塞一组线程直到某个事件发生。栅栏和闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行闭锁用于等待事件,而栅栏用于等待其他线程。CyclicBarrier的计数器由自己控制,而CountDownLatch的计数器则由使用者来控制,在CyclicBarrier中线程调用await方法不仅会将自己阻塞还会将计数器减1,而在CountDownLatch中线程调用await方法只是将自己阻塞而不会减少计数器的值。

闭锁是一次性对象,一旦进入终止状态,就不能重置,只能拦截一轮。而栅栏可以多次使用。

应用案例

赛马程序,CyclicBarrier使得每匹马都要执行为了向前移动所需要执行的所有动作,然后必须在栅栏处等待其他所有马都准备完毕。一旦所有的任务都越过栅栏,他会自动为下一回合比赛做好准备

class Horse implements Runnable {private static int counter = 0;private final int id = counter++;private int strides = 0;private static Random rand = new Random(47);private static CyclicBarrier barrier;public Horse(CyclicBarrier b) { barrier = b; }@Overridepublic void run() {try {while(!Thread.interrupted()) {synchronized(this) {//赛马每次随机跑几步strides += rand.nextInt(3);}barrier.await();}} catch(Exception e) {e.printStackTrace();}}public String tracks() {StringBuilder s = new StringBuilder();for(int i = 0; i < getStrides(); i++) {s.append("*");}s.append(id);return s.toString();}public synchronized int getStrides() { return strides; }public String toString() { return "Horse " + id + " "; }}public class HorseRace implements Runnable {private static final int FINISH_LINE = 75;private static List<Horse> horses = new ArrayList<Horse>();private static ExecutorService exec = Executors.newCachedThreadPool();@Overridepublic void run() {StringBuilder s = new StringBuilder();//打印赛道边界for(int i = 0; i < FINISH_LINE; i++) {s.append("=");}System.out.println(s);//打印赛马轨迹for(Horse horse : horses) {System.out.println(horse.tracks());}//判断是否结束for(Horse horse : horses) {if(horse.getStrides() >= FINISH_LINE) {System.out.println(horse + "won!");exec.shutdownNow();return;}}//休息指定时间再到下一轮try {TimeUnit.MILLISECONDS.sleep(200);} catch(InterruptedException e) {System.out.println("barrier-action sleep interrupted");}}public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace());for(int i = 0; i < 7; i++) {Horse horse = new Horse(barrier);horses.add(horse);exec.execute(horse);}}}

该赛马程序主要是通过在控制台不停的打印各赛马的当前轨迹,以此达到动态显示的效果。整场比赛有多个轮次,每一轮次各个赛马都会随机走上几步然后调用await方法进行等待,当所有赛马走完一轮的时候将会执行任务将所有赛马的当前轨迹打印到控制台上。

这样每一轮下来各赛马的轨迹都在不停的增长,当其中某个赛马的轨迹最先增长到指定的值的时候将会结束整场比赛,该赛马成为整场比赛的胜利者!程序的运行结果如下:

CyclicBarrier栅栏相关推荐

  1. 聊聊高并发(三十)解析java.util.concurrent各个组件(十二) 理解CyclicBarrier栅栏

    这篇讲讲CyclicBarrier栅栏,从它的名字可以看出,它是可循环使用的.它的功能和CountDownLatch类似,也是让一组线程等待,然后一起开始往下执行.但是两者还是有几个区别 1. 等待的 ...

  2. CyclicBarrier(栅栏)实现高并发测试

    public class TestCyclic {@Testpublic void test01() {int count = 10000;//并发线程数CyclicBarrier cyclicBar ...

  3. Java多线程通信-CyclicBarrier(栅栏)

    一.CyclicBarrier(栅栏) 通过闭锁,我们可以启动一组相关的操作.或者等待一组相关的操作结束.闭锁是一次性对象,到达终止状态后将不可用.     CyclicBarrier与闭锁类似,能够 ...

  4. 【代码】CyclicBarrier栅栏使用示例

    一批一批的放行. import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarri ...

  5. JAVA 并发编程之三:CountDownLatch(门闩)、CyclicBarrier(栅栏)和Semaphore(信号量) 三种并发策略

    在JDK的并发包中已经提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类中提供了一种并发流程控制的手段,Exchanger工具类提供了在 ...

  6. java线程栅栏_Java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

    今天项目上遇到一个多线程任务问题,大概图文描述一下: 1.前端需要及时返回任务状态 2.后台开了一个任务线程去执行具体的业务,业务包括四个部分,四个部分全部完成才算完成 3.业务中某些耗时的或者需要多 ...

  7. java concurrent 栅栏,java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

    [一:java并发的开篇 1.在实际应用中,经常用到线程的并发,那为什么需要用到并发呢,不能独自单独的程序处理吗,那很明确的说,多条线程做完成一件事情和一条线程去完成 -闭锁(Latch) 闭锁(La ...

  8. Java并发包之闭锁/栅栏/信号量(转)

    本文转自http://blog.csdn.net/u010942020/article/details/79352560 感谢作者 一.Java多线程总结: 描述线程的类:Runable和Thread ...

  9. Java并发编程之CyclicBarrier和CountDownLatch

    1.CyclicBarrier简介 CyclicBarrier(栅栏):拦截一组线程并使其阻塞,直到其内部的计数器归零,再唤醒所有的阻塞线程继续执行任务. 基础属性 public class Cycl ...

  10. java 信号量 闭锁_Java并发包之闭锁/栅栏/信号量

    二.同步工具类详解 1.Semaphore信号量:跟锁机制存在一定的相似性,semaphore也是一种锁机制,所不同的是,reentrantLock是只允许一个线程获得锁,而信号量持有多个许可(per ...

最新文章

  1. Javascript使用三大家族和事件来DIY动画效果相关笔记(一)
  2. 谢文睿:西瓜书 + 南瓜书 吃瓜系列 9. 集成学习(上)
  3. Functional Programming Contest - September'14
  4. Java Web整合开发(85)
  5. Tensorflow矩阵过大问题的解决
  6. NYOJ 833 取石子(七)
  7. Windows下Core Audio APIs的使用简介
  8. oracle中通过游标实现查询
  9. TCP/IP之(四)Delay ack 和 Nagle算法
  10. java优先队列_Java高级特性增强-多线程
  11. 金明的预算方案(洛谷-P1064)
  12. header python 环境信息_python获取网页header头部信息(python小白学习笔记二)
  13. .net core 学习小结之 JWT 认证授权
  14. springboot整合哨兵模式连接redis
  15. [hgo学习]-tutorial 03
  16. Android照片墙应用实现,再多的图片也不怕崩溃
  17. oracle之Number类型小数转字符串丢精度
  18. 【积水成渊-逐步定制自己的Emacs神器】4:Emacs自动补全
  19. Spring Boot多模块项目打包
  20. 五款堪称神器的网页翻译插件,不知道就亏大了!

热门文章

  1. unity开发记录:TextMeshPro设置显示中文
  2. Java实现动态规划经典题目
  3. EXCEL区分两列名单中不重复的人,以及统计单列名单人员的重复次数
  4. UVA 10066 10192
  5. 计算机毕设 SpringBoot 校园志愿者管理系统 志愿者管理系统 志愿者信息管理系统Java Vue MySQL数据库 远程调试 代码讲解
  6. 电影说明里何谓枪版?何谓TS版?TC版?
  7. 点云配准(一) 线性代数基础
  8. python中将字符变为大写_python如何把小写字母变成大写字母
  9. linux0.11主存管理程序阅读注释笔记
  10. java计算机毕业设计交通事故档案管理系统源程序+mysql+系统+lw文档+远程调试