并发编程:CyclicBarrier

CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。


通俗点讲就是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

CyclicBarrier字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier

当调用await()方法之后,线程就处于barrier了。


先来个demo:

并发编程:CyclicBarrier_第1张图片

并发编程:CyclicBarrier_第2张图片

运行结果

并发编程:CyclicBarrier_第3张图片

代码中设置了两个屏障点,第一个用于召集7个法师,等7个法师召集完后,在设置在一个屏障点,7位法师去寻找龙珠,然后召唤神龙,中间有个嵌套的关系!


源码分析

上述的例子,大致说了一下屏障,因为设置了两个屏障,并没有演示上述说的可循环使用(Cyclic)的屏障(Barrier) 中的可循环使用(Cyclic)。

并发编程:CyclicBarrier_第4张图片

查看 CyclicBarrier.reset() 可知,可以使 CyclicBarrier 回到最初始的状态,由于使用的相对较少,这里不再演示。

CyclicBarrier构造方法

并发编程:CyclicBarrier_第5张图片

并发编程:CyclicBarrier_第6张图片

并发编程:CyclicBarrier_第7张图片

可以看得出CyclicBarrier的内部是使用重入锁ReentrantLock和Condition。它有两个构造函数:

CyclicBarrier(int parties):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

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

parties表示拦截线程的数量。

barrierAction 为CyclicBarrier接收的Runnable命令,用于在线程到达屏障时,优先执行barrierAction ,用于处理更加复杂的业务场景。

在CyclicBarrier中最重要的方法莫过于await()方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。如下:

并发编程:CyclicBarrier_第8张图片

第二个参数0,说明不是超时等待

并发编程:CyclicBarrier_第9张图片

并发编程:CyclicBarrier_第10张图片

其实await()的处理逻辑还是比较简单的:如果该线程不是到达的最后一个线程,则他会一直处于等待状态,除非发生以下情况:

  1. 最后一个线程到达,即index == 0

  2. 超出了指定时间(超时等待)

  3. 其他的某个线程中断当前线程

  4. 其他的某个线程中断另一个等待的线程

  5. 其他的某个线程在等待barrier超时

  6. 其他的某个线程在此barrier调用reset()方法。reset()方法用于将屏障重置为初始状态。

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

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

并发编程:CyclicBarrier_第11张图片

默认barrier是没有损坏的

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

并发编程:CyclicBarrier_第12张图片

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

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

  • 唤醒所有线程

  • 重置count

  • generation

并发编程:CyclicBarrier_第13张图片

CyclicBarrier同时也提供了await(long timeout, TimeUnit unit) 方法来做超时控制:

并发编程:CyclicBarrier_第14张图片

可以看出其内部还是通过调用

private int dowait(boolean timed, long nanos)

实现的。


CyclicBarrier CountDownLatch 的区别

CountDownLatch 的计数器只能使用一次。而 CyclicBarrier 的计数器可以使用 reset() 方法重置。所以 CyclicBarrier 能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。

CyclicBarrier 还提供其他有用的方法,比如 getNumberWaiting 方法可以获得 CyclicBarrier 阻塞的线程数量。isBroken 方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回 true。

CountDownLatch 会阻塞主线程,CyclicBarrier 不会阻塞主线程,只会阻塞子线程。

你可能感兴趣的:(并发编程:CyclicBarrier)