引言及简介
上一章我们介绍了独占式的同步组件ReentrantLock,今天我们先不忙着继续介绍其他Lock接口的实现类同步组件,先来看看JDK基于ReentrantLock同步组件以及Condition条件等待机制实现的一个同步辅助器CyclicBarrier。
CyclicBarrier是一个同步辅助工具,它允许一组线程相互等待达到一个公共的屏障(barrier)点。CyclicBarrier的字面意思可以叫做可循环(Cyclic)使用的屏障(Barrier),之所以说它是可循环(Cyclic)使用的屏障,是因为当所有相互等待的线程都释放之后(或者说都通过屏障之后),它是可以被重复使用的。另外,CyclicBarrier还可以让这一组相互等待的线程在都到达这个公共的屏障(barrier)之后,让最晚到达屏障的线程先去完成一件特殊指定的任务,在这项指定的任务完成之后,再让这一组线程接着往下执行(可能已经执行完成就意味着结束),包括最晚到达屏障的那一个线程也会继续往下执行自己本来的逻辑。
可能通过这样的描述,我们很难理解CyclicBarrier到底有什么作用,以我的理解可以简单的描述为:使用CyclicBarrier可以使一组线程在至少有其中一个线程没有就位之前都让已经就位的线程陷入等待,直到所有的线程都就位之后,大家再一起继续往下执行(如果已经执行完就意味着结束)。所谓的“就位”就相当于是一个屏障或者一扇门,只有当所有的参与者都来到这扇大门前,门才会开启让大家一起通过,在大家通过之后,这扇门又会关闭复原,使其能够被重复用于进行“就位”等待放行使用。作为可选的特殊任务就是在大家都就位之后,在门打开之前的这一个间隙之间,可以让最晚就位的参与者临时去完成一件特殊任务(谁让你来的最晚呢),在这件任务完成之后,大家再一起通过这扇门,然后该干嘛干嘛。
上图是我理解的CyclicBarrier,线程A,B,C花费不同的时间之后达到一个公共的屏障之后,再一起继续往后执行(当然也可以是后面没有逻辑需要执行而结束),在线程B达到之前,线程C已经等待了5秒,线程A已经等待了3秒,如果有指定一个在屏障点的特殊任务,那么在线程A,B,C通过屏障继续往下执行之前,将由线程B(因为它最晚到达)去完成该特殊任务。
使用示例
通过上面的简介,我相信应该对CyclicBarrier的作用有了了解,下面我们就使用几个示例来加深对其了解。在使用之前,我们先要了解一下CyclicBarrier的构造方法,它有两个构造方法:
public CyclicBarrier(int parties, Runnable barrierAction) { //接受一个在屏障点的特殊任务barrierAction if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } //直接构造一个CyclicBarrier,它将使指定个数的参与者(线程)都到达屏障点之前相互等待 public CyclicBarrier(int parties) { this(parties, null); }
通过构造方法我们可以发现,在屏障点的特殊任务是可选的,该任务是一个实现了Runnable接口的实例,整形参数parties表示让多少个线程相互等待至屏障点。
示例一:假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情
public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N, new Runnable(){ @Override public void run() { System.out.println("由线程"+Thread.currentThread().getName()+"开始执行屏障点特殊任务"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); 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((long)(Math.random() * 10000)); //以睡眠来模拟写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); int index = cyclicBarrier.await(); System.out.println("所有线程写入完毕,当前线程"+Thread.currentThread().getName()+"(await返回:"+index+")继续处理其他任务..."); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } } }
执行结果:
线程Thread-2正在写入数据... 线程Thread-1正在写入数据... 线程Thread-3正在写入数据... 线程Thread-0正在写入数据... 线程Thread-0写入数据完毕,等待其他线程写入完毕 线程Thread-2写入数据完毕,等待其他线程写入完毕 线程Thread-3写入数据完毕,等待其他线程写入完毕 线程Thread-1写入数据完毕,等待其他线程写入完毕 由线程Thread-1开始执行屏障点特殊任务 所有线程写入完毕,当前线程Thread-1(await返回:0)继续处理其他任务... 所有线程写入完毕,当前线程Thread-0(await返回:3)继续处理其他任务... 所有线程写入完毕,当前线程Thread-2(await返回:2)继续处理其他任务... 所有线程写入完毕,当前线程Thread-3(await返回:1)继续处理其他任务...
通过示例一可以看到,我们构造了四个相互等待的线程,在线程Thread-1没有写入数据完毕之前,其它三个最快的线程一直等待,当四个线程都到达屏障点时,让最晚执行完的线程Thread-1执行了屏障点的特殊任务,特殊任务执行完之后,四个线程再一起继续往后执行。并且最早达到的线程Thread-0的await()的返回值为3,往后依次递减,最晚达到屏障点的线程Thread-1的await()的返回值为0,
示例二:继续改造示例一,演示 CyclicBarrier的可重用性。
public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N,new Runnable() { @Override public void run() { System.out.println("由线程"+Thread.currentThread().getName()+"开始执行屏障点特殊任务"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); 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() { int count = 2; try { while (count > 0) { count--; System.out.println("线程"+Thread.currentThread().getName()+"第"+(2-count)+"次正在写入数据..."); Thread.sleep(5000); //以睡眠来模拟写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"第"+(2-count)+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await(); } System.out.println("所有线程写入完毕,当前线程"+Thread.currentThread().getName()+"继续处理其他任务..."); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } } }
执行结果:
线程Thread-0第1次正在写入数据... 线程Thread-2第1次正在写入数据... 线程Thread-3第1次正在写入数据... 线程Thread-1第1次正在写入数据... 线程Thread-3第1写入数据完毕,等待其他线程写入完毕 线程Thread-1第1写入数据完毕,等待其他线程写入完毕 线程Thread-0第1写入数据完毕,等待其他线程写入完毕 线程Thread-2第1写入数据完毕,等待其他线程写入完毕 由线程Thread-2开始执行屏障点特殊任务 线程Thread-2第2次正在写入数据... 线程Thread-0第2次正在写入数据... 线程Thread-1第2次正在写入数据... 线程Thread-3第2次正在写入数据... 线程Thread-3第2写入数据完毕,等待其他线程写入完毕 线程Thread-2第2写入数据完毕,等待其他线程写入完毕 线程Thread-1第2写入数据完毕,等待其他线程写入完毕 线程Thread-0第2写入数据完毕,等待其他线程写入完毕 由线程Thread-0开始执行屏障点特殊任务 所有线程写入完毕,当前线程Thread-0继续处理其他任务... 所有线程写入完毕,当前线程Thread-3继续处理其他任务... 所有线程写入完毕,当前线程Thread-2继续处理其他任务... 所有线程写入完毕,当前线程Thread-1继续处理其他任务...
在示例二中,我让这四个线程循环执行两次模拟数据写入操作,所以他们会遇到两次CyclicBarrier设定的屏障,再他们第一次通过屏障之后,屏障将会被复原,所以第二次遇到屏障的时候,依然会产生和第一次相同的效果,当然这四个线程具体的执行顺序几乎不会和第一次相同了,执行屏障点的特殊任务的线程还是由最晚达到屏障点的线程去完成,第一次是线程Thread-2,第二次是线程Thread-0.
通过以上的两个示例我们只能基本的了解CyclicBarrier循环屏障的简单使用,至于其深层次的其他特性,比如:万一存在线程在未到达屏障之前出现了未被捕获的异常,或者线程在执行屏障点的特殊任务的时候抛出了异常,那么又会对其他线程的执行有什么影响呢?这些问题我们稍后再来回答,虽然我们可以通过继续举例来得到验证,但是我还是想先看看CyclicBarrier的源码之后再进行说明,毕竟举例不可能把所有的情况都包含,通过源码分析才能得到最直接最有力的结论。
源码分析
首先我们看看CyclicBarrier的成员属性:
public class CyclicBarrier { private static class Generation { boolean broken = false; } //锁对象 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(); //表示还未达到屏障点的线程个数,为0表示所有线程都达到了屏障点 private int count; .... }通过以上对成员属性的分析,可以发现,CyclicBarrier确实是基于ReentrantLock同步组件以及Condition条件等待机制实现的,另外,CyclicBarrier还存在一个私有的静态内部类Generation,它只有一个布尔型的broken属性,用于表示当前屏障是否被破坏,在默认情况下或者所有参与者都通过屏障之后屏障都是没有被破坏的。Generation其实也是实现CyclicBarrier能够被重复使用的关键。
接着分析CyclicBarrier最核心的方法,通过上面的示例可以发现,CyclicBarrier最核心的关系方法就是await()方法,所以我们直接看这个方法就可以了,当然CyclicBarrier内部还提供了带有超时时间的await()方法。
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L);//第一个参数为false.超时时间为0,表示没有超时设定 } catch (TimeoutException toe) { throw new Error(toe); // 因为dowait()的时间参数为0, 所以这里永远不会抛出TimeoutException。 } } //带有超时时间的await()方法 public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); //因为dowait()的时间参数由使用者传入,所以有可能会抛出TimeoutException异常}从表面上可以看到await()是可以抛出中断异常和BrokenBarrierException异常的,甚至await(long, TimeUnit)方法还可能抛出TimeoutException超时异常,他们都是调用的dowait()方法,其返回值是一个由dowait()方法返回的整形数值,通过Java Doc的描述,await()/dowait()方法的返回值代表当前线程到达屏障点的序列号,假设有5个线程,最先到达屏障点的线程该方法返回值的就是4,往后依次递减,最后一个达到屏障点的线程调用该方法的返回值就是0.
相关推荐
### Java并发包详解:深入理解Java并发编程 #### 3.1 java.util.concurrent概述 `java.util.concurrent`包自JDK 5.0版本引入,是Java并发编程的核心,旨在利用现代多处理器和多核心系统的优势,提升大规模并发应用...
16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService 19. 使用 ForkJoinPool 进行分叉和合并 20. 锁 Lock 21. 读写锁 ReadWriteLock 22. 原子性布尔 ...
Java 5并发包(`java.util.concurrent`,简称`Concurrent`包)是Java平台中用于多线程编程的重要组成部分,它提供了丰富的并发工具类,极大地简化了在多线程环境下的编程工作。这个包的设计目标是提高并发性能,减少...
java并发编程中CountDownLatch和CyclicBarrier的使用借鉴 java并发编程中CountDownLatch和CyclicBarrier是两个非常重要的线程控制和调度工具,经常被用于解决多线程程序设计中的线程等待问题。本文将对...
在Java并发编程中,CountDownLatch和CyclicBarrier是两种非常重要的同步工具,用于协调多个线程之间的交互。它们都属于java.util.concurrent包下的类,为多线程编程提供了强大的支持。 **CountDownLatch** 是一个...
在Java的并发编程中,CountDownLatch和CyclicBarrier是两个非常重要的同步工具,它们用于协调多个线程的执行顺序。本文将详细介绍CountDownLatch和CyclicBarrier的工作原理、使用场景以及如何在实际项目中应用它们。...
Java中将异步调用转换为同步调用有多种方法,本文将详细介绍五种方法:使用wait和notify方法、使用条件锁、使用Future、使用CountDownLatch、使用CyclicBarrier。 1. 使用wait和notify方法 wait和notify方法是...
JUC提供了丰富的并发原语,如线程池、同步器、并发容器等,极大地简化了多线程编程的复杂性,并提升了程序的性能和可维护性。以下是对JUC库的详细解析: 1. **线程池(ExecutorService)**: - `ExecutorService`...
CyclicBarrier是Java并发包(java.util.concurrent)中一个重要的同步辅助类,它的主要作用在于协调多个线程之间的协作,使得这些线程能够一起到达一个公共的“集结点”(称为屏障点)。在多线程编程中,尤其是在...
5. **CountDownLatch** 和 **CyclicBarrier**:这两个工具类可以帮助我们实现线程间的同步,例如,CountDownLatch可以让一组线程等待其他线程完成操作,而CyclicBarrier则允许一组线程到达一个屏障(或同步点)后...
3. **同步工具类**:Java并发包`java.util.concurrent`中的工具类,如`Semaphore`(信号量)、`CyclicBarrier`(回环栅栏)、`CountDownLatch`(倒计时器)和`FutureTask`(未来任务)等,提供了更灵活的线程同步和...
- **java.util.concurrent** 包提供了更高级的同步工具,如 `ReentrantLock`、`Semaphore` 和 `CyclicBarrier` 等,它们提供了更灵活的控制和更好的性能。 - **volatile** 关键字:用于标记变量,使得多线程环境中...
为此,Java提供了多种机制来帮助实现线程间的协作与同步,其中包括了`CyclicBarrier`类。 ### 1.2 CyclicBarrier概述 `CyclicBarrier`是Java并发工具包(`java.util.concurrent`)中的一个强大工具。它的主要作用...
Java多线程之CyclicBarrier的使用方法是Java多线程编程中的一种同步机制,用于实现多个线程之间的同步协作。CyclicBarrier是Java 5中引入的一种同步机制,用于让多个线程等待到达一个共同的屏障点,直到所有线程都...
Java并发实例之CyclicBarrier的使用 CyclicBarrier是Java中的一种多线程并发控制实用工具,和CountDownLatch非常类似,它也可以实现线程间的计数等待,但是它的功能比CountDownLatch更加复杂且强大。CyclicBarrier...
Java并发包中的某些类如`AtomicInteger`、`ConcurrentHashMap`等是线程安全的,无需额外的同步措施。 2. **Executor框架**:`java.util.concurrent.Executor`是执行任务的核心接口,它定义了运行任务的方法。`...
高级实用工具类是`java.util.concurrent`的核心,包括线程安全集合(如ConcurrentHashMap、CopyOnWriteArrayList等)、线程池(ExecutorService、ThreadPoolExecutor、ScheduledThreadPoolExecutor等)、信号...
在Java多线程编程中,`CyclicBarrier`是一个非常重要的同步工具类,它允许一组线程等待其他线程到达某个屏障点后再一起继续执行。这个屏障点就是我们所说的“循环栅栏”,顾名思义,它就像一个旋转门,所有线程必须...
另外,Java的并发包(java.util.concurrent)提供了丰富的方法和工具,如Semaphore(信号量)、CyclicBarrier(循环屏障)和CountDownLatch(倒计时器)等,帮助开发者更高效地管理同步问题。 在实际应用中,开发者...