一、CyclicBarrier简介
CyclicBarrier循环栅栏是让一组线程达到一个栅栏(屏障)时被阻塞,直到最后一个线程到达栅栏时,栅栏才会开门。简单的说CyclicBarrier也是一个计数器,不过这个技术器有一个栅栏,等所有线程都到达栅栏后才会去继续执行后面的程序。
CyclicBarrier循环栅栏和闭锁CountDownLatch很像,不过和闭锁的主要区别在于,所有线程必须同时到达屏障点,才能继续执行,闭锁用于等待事件,而栅栏用于实现一些协议。比如:长途大巴行驶到服务器,自由活动20分钟,之后司机在等待最后一个乘客上车之后,才会出发驶出服务区,开始新的行程。
二、使用案例:
在下面的例子中,栅栏cb设置为2,执行程序会看到打印结果为1和2或2和1,造成这样的原因的是线程的执行顺序是由CPU决定的。至此好像也没有体现出CyclicBarrier的特点。不过当cb的初识值设置为2以上时,比如说3,再执行程序时,程序会一直阻塞下去。因为本身就没有第三条线程会执行,所有就会一直等待。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrier循环栅栏
* 使用案例
*/
public class CyclicBarrierTest {
static CyclicBarrier cb = new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
cb.await();
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
try {
cb.await();
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
三、内部结构及实现原理:
来看下CyclicBarrier的内部结构,一探究竟。
内部类:当前栅栏,仅包含一个状态值,默认为false(栅栏完成),当线程超时或者中断时,会被修改为true,表示栅栏已经被破坏。
private static class Generation {
boolean broken = false;
}
构造方法:
//包含栅栏的大小和有继续执行任务
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
//只包含栅栏大小,调用CyclicBarrier(int parties, Runnable barrierAction),
继续执行任务设置为null
public CyclicBarrier(int parties) {
this(parties, null);
}
重要的成员变量:
/** 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.
*/
//仍在等待的派对数量。从派对倒计时到0,初始值就是栅栏的大小paities,
//每有一个线程到达栅栏都会自减1
private int count;
主要方法:
进入栅栏:无论是否含有参数,进入栅栏都是通过可重入锁ReentrantLock来锁住栅栏的入口。
//不含参数
public int await() throws InterruptedException, BrokenBarrierException {...}
//含有超时时间
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
1-1无论是否设置超时时间,调用的方法都是dowait方法,首先先加锁,判断栅栏的状态是正常,如果已经损坏在抛错BrokenBarrierException。继续判断线程是否中断,如果线程已经中断,先修改栈蓝状态为损坏,同时抛错InterruptedException。
1-2倒计数,如果一个线程到达栅栏则自减1,如果此时index==0,说明所有线程都到达了栅栏,如果还有线程其他任务执行则barrierCommand线程任务。紧接着执行nextGeneration(),唤醒所有线程继续执行,恢复派对数量,同时更新了生成一个新的栅栏。需要注意的是必须是持有同一把锁期间。如果再唤醒所有线程之前出现异常,则调用breakBarrier()方法,唤醒所有对象,将当前的栅栏状态更新为已损坏。
1-3、自旋直至跳闸、断裂、中断或超时,接着判断栅栏是否被破坏,如果已被破坏则抛错BrokenBarrierException ,超时判断,如果超时则调用breakBarrier()方法,唤醒所有对象,将当前的栅栏状态更新为已损坏,抛错超时异常TimeoutException,最后释放锁。
//进入栅栏
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//1-1
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()) {
breakBarrier();//唤醒所有对象,将当前的栅栏状态更新为已损坏
throw new InterruptedException();
}
//1-2步骤
int index = --count;//派对数量自减1
//表示所有线程都已阻塞在栅栏
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();//继续执行其他任务
ranAction = true;
nextGeneration();//唤醒所有线程,新建栅栏,恢复派对数量
return 0;
} finally {
if (!ranAction)//其他任务执行异常
breakBarrier();
}
}
// 1-3 自旋直至跳闸、断裂、中断或超时
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();//是放锁
}
}
//唤醒所有对象,将当前的栅栏状态更新为已损坏
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
//唤醒所有线程,新建栅栏,恢复派对数量
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
重置栅栏:reset()
破坏当前的栅栏,重建新的新的栅栏。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
参考书籍:
方志明 著《Java并发编程艺术》
Doug lea 等著《Java并发编程实战》