简介

栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

CyclicBarrier源码解析

CyclicBarrier的类图如下:

通过类图我们可以看到,CyclicBarrier内部使用了ReentrantLock和Condition两个类。它有两个构造函数:

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

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。

await方法

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

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(boolean, long)方法,它也是CyclicBarrier的核心方法,该方法定义如下:

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) {  // trippedboolean ranAction = false;try {final Runnable command = barrierCommand;// 执行栅栏任务if (command != null)command.run();ranAction = true;// 更新一代,将count重置,将generation重置// 唤醒之前等待的线程nextGeneration();return 0;} finally {// 如果执行栅栏任务的时候失败了,就将损坏状态设置为trueif (!ranAction)breakBarrier();}}// loop until tripped, broken, interrupted, or timed outfor (;;) {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();}
}

dowait(boolean, long)方法的主要逻辑处理比较简单,如果该线程不是最后一个调用await方法的线程,则它会一直处于等待状态,除非发生以下情况:

  • 最后一个线程到达,即index == 0
  • 某个参与线程等待超时
  • 某个参与线程被中断
  • 调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态

在上面的源代码中,我们可能需要注意Generation 对象,在上述代码中我们总是可以看到抛出BrokenBarrierException异常,那么什么时候抛出异常呢?如果一个线程处于等待状态时,如果其他线程调用reset(),或者调用的barrier原本就是被损坏的,则抛出BrokenBarrierException异常。同时,任何线程在等待时被中断了,则其他所有线程都将抛出BrokenBarrierException异常,并将barrier置于损坏状态。

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

private static class Generation {boolean broken = false;
}

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

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

在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。

当所有线程都已经到达barrier处(index == 0),则会通过nextGeneration()进行更新换地操作,在这个步骤中,做了三件事:唤醒所有线程,重置count,generation:

private void nextGeneration() {// signal completion of last generationtrip.signalAll();// set up next generationcount = parties;generation = new Generation();
}

除了上面讲到的栅栏更新换代以及损坏状态,我们在使用CyclicBarrier时还要要注意以下几点:

  • CyclicBarrier使用独占锁来执行await方法,并发性可能不是很高
  • 如果在等待过程中,线程被中断了,就抛出异常。但如果中断的线程所对应的CyclicBarrier不是这代的,比如,在最后一次线程执行signalAll后,并且更新了这个“代”对象。在这个区间,这个线程被中断了,那么,JDK认为任务已经完成了,就不必在乎中断了,只需要打个标记。该部分源码已在dowait(boolean, long)方法中进行了注释。
  • 如果线程被其他的CyclicBarrier唤醒了,那么g肯定等于generation,这个事件就不能return了,而是继续循环阻塞。反之,如果是当前CyclicBarrier唤醒的,就返回线程在CyclicBarrier的下标。完成了一次冲过栅栏的过程。该部分源码已在dowait(boolean, long)方法中进行了注释。

应用程序示例

我们看一个CyclicBarrier的应用示例:

public class CyclicBarrierTest {// 自定义工作线程private static class Worker extends Thread {private CyclicBarrier cyclicBarrier;public Worker(CyclicBarrier cyclicBarrier) {this.cyclicBarrier = cyclicBarrier;}@Overridepublic void run() {super.run();try {System.out.println(Thread.currentThread().getName() + "开始等待其他线程");cyclicBarrier.await();System.out.println(Thread.currentThread().getName() + "开始执行");// 工作线程开始处理,这里用Thread.sleep()来模拟业务处理Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + "执行完毕");} catch (Exception e) {e.printStackTrace();}}}public static void main(String[] args) {int threadCount = 3;CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);for (int i = 0; i < threadCount; i++) {System.out.println("创建工作线程" + i);Worker worker = new Worker(cyclicBarrier);worker.start();}}
}

运行结果(不唯一):

创建工作线程0
创建工作线程1
Thread-0开始等待其他线程
创建工作线程2
Thread-1开始等待其他线程
Thread-2开始等待其他线程
Thread-2开始执行
Thread-0开始执行
Thread-1开始执行
Thread-1执行完毕
Thread-0执行完毕
Thread-2执行完毕

在上述代码中,我们自定义的工作线程必须要等所有参与线程开始之后才可以执行,我们可以使用CyclicBarrier类来帮助我们完成。从程序的执行结果中也可以看出,所有的工作线程都运行await()方法之后都到达了栅栏位置,然后,3个工作线程才开始执行业务处理。

CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;

CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;

CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

相关博客

AbstractQueuedSynchronizer同步队列详解

AbstractQueuedSynchronizer独占式同步状态获取与释放

Java并发编程之ReentrantLock详解

Java并发编程之Condition详解

参考资料

方腾飞:《Java并发编程的艺术》

Doug Lea:《Java并发编程实战》

【死磕Java并发】-----J.U.C之并发工具类:CyclicBarrier

Java并发编程之CyclicBarrier详解相关推荐

  1. Java并发编程之ConcurrentLinkedQueue详解

    简介 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式一种是使用阻塞算法,另一种是使用非阻塞算法.使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两 ...

  2. Java并发编程之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  3. java并发编程之Volatile详解

    前言 在Java中多个线程对公共变量的操作并不是直接在内存中操作的,每一个线程都会有一块自己的工作内存.线程会先从主内存中获取到变量的值到工作内存中进行修改在更新到主内存.假如有两个线程同时对某个变量 ...

  4. 并发编程专题——第二章(并发编程之Synchronized详解)

    日常中我们都会用到Synchronized关键字,但是面试就喜欢问这些,你说不重要吧,面试就不问了,你说重要吧,工作中除了高并发之外,很少能在业务代码中使用到的.所以笔者顶着风险,写下此篇对Synch ...

  5. Java并发工具类--CyclicBarrier详解

    CyclicBarrier允许一组线程在到达某个栅栏点(common barrier point)互相等待,直到最后一个线程到达栅栏点,栅栏才会打开,处于阻塞状态的线程恢复继续执行. 举例 举个例子来 ...

  6. Java并发编程之CyclicBarrier

    CyclicBarrier 运行原理图 假如有3个线程,内部运行时都使用了同一个CyclicBarrier,假如Thread1先到达屏障点,那么此时由于Thread2和Thread3还未到达,此时Th ...

  7. Java并发编程之CyclicBarrier和CountDownLatch

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

  8. cyclicbarrier java_Java并发编程之CyclicBarrier和线程池的使用

    原标题:Java并发编程之CyclicBarrier和线程池的使用 下面我们来讲述一下线程池和CyclicBarrier的使用和对比. 一.场景描述 有四个游戏玩爱好者玩游戏,游戏中有三个关卡,每一个 ...

  9. Java并发编程最佳实例详解系列

    Java并发编程最佳实例详解系列: Java并发编程(一)线程定义.状态和属性 Java并发编程(一)线程定义.状态和属性 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中, ...

最新文章

  1. js最简单的几个特效_高阶函数不会用?教你JS中最实用的几个高阶函数用法
  2. 运维实战案例之文件已删除但空间不释放问题解析
  3. Linux高性能网络:协程系列09-协程性能测试
  4. 用户登录邮箱验证和发送邮件缓存应用
  5. 百度搜索结果URL参数含义解析
  6. HTML 标签 参考手册
  7. python 中用什么键缩进 —— tab 还是空格?
  8. Android ListView分页,动态添加数据
  9. jsp java代码_jsp内的java代码不执行,急啊!!大家来看看!
  10. 取代ZooKeeper!高并发下的分布式一致性开源组件StateSynchronizer
  11. 关于CAS服务器磁盘占用的问题,锁定目录惹的祸
  12. Struts2学习笔记(六) 结果(Result)(上)
  13. 数据结构07之哈希表
  14. 安卓新手入门基础知识
  15. 怎么找网页源文件位置_win7系统查看网页源文件的三种方法
  16. OrCAD打开工程报错-ERROR(ORCAP-1653)
  17. MIT | 数据分析、信号处理和机器学习中的矩阵方法 笔记系列: Lecture 8 Norms of Vectors and Matrices
  18. java图片转单色位图_Android:将位图转换为单色位图(每像素1位)
  19. 内存颗粒性能测试软件,内存性能测试及编辑总结
  20. 蓝牙(BLE)自动配对

热门文章

  1. 小浩病毒专杀工具下载
  2. 大快搜索命名集群时一直出现该名称已存在
  3. java7或java8新特性
  4. 【Android】adb命令和adb shell命令
  5. 关于CSMA/CA和CSMA/CD的区别
  6. via css书签,Unicode via CSS :before
  7. ffmpeg- 部分错误码-av_interleaved_write_frame/av_write_frame
  8. 智慧校园水电节能监管系统
  9. 蓝桥杯 基础练习 报时助手(c++)
  10. spring框架有哪些优点