简介
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
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默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
CyclicBarrier的类图如下:
通过类图我们可以看到CyclicBarrier内部使用了ReentrantLock和Condition两个类。
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry 锁*/
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped 状态转换*/
private final Condition trip = lock.newCondition();
/** The number of parties 初始化的栏总数*/
private final int parties;
/* The command to run when tripped 处理完相应数量的线程后执行的逻辑*/
private final Runnable barrierCommand;
/** The current generation (是否执行完)*/
private Generation generation = new Generation();
/**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
等待处理的个数
*/
private int count;
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 CyclicBarrier.Generation g = generation;
if (g.broken) //判断当前栏是否处理完成
throw new BrokenBarrierException();
if (Thread.interrupted()) { //若线程状态中断
breakBarrier();//唤醒所有的等待线程,计数清零,本局状态结束
throw new InterruptedException();
}
int index = --count; //未处理数-1
if (index == 0) { // tripped 如果全部处理了
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)//判断是否有处理函数,有则执行
command.run();
ranAction = true;
nextGeneration();//唤醒所有,计数重置,处理状态未false
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
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 {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
// g != generation表示正常换代了,返回当前线程所在栅栏的下标
// 如果 g == generation,说明还没有换代,那为什么会醒了?
// 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
// 正是因为这个原因,才需要generation来保证正确。
if (g != generation)
return index;
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();
}
当所有线程都已经到达barrier处(index == 0),则会通过nextGeneration()进行更新换地操作,在这个步骤中,做了三件事:唤醒所有线程,重置count,generation:
private void nextGeneration() {
// 唤醒全部等待的线程
trip.signalAll();
// 重置count 和generation
count = parties;
generation = new Generation();
}
在要多次使用CyclicBarrier就不得不说reset(),在这个步骤中做了上面的2件事情 breakBarrier和nextGeneration
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
下面写一个例子吧,摘抄其他帖子的跑马程序~~~~)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
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; }
@Override
public void run() {
try {
while(!Thread.interrupted()) { //这个可以一直循环。。。。
synchronized(this) {
System.out.println("----------");
//赛马每次随机跑几步
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();
@Override
public 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);
}
}
}
总结
CyclicBarrier底层实现是ReentrantLock和Condition来实现的
CyclicBarrier和CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。