一. CyclicBarrier使用的场景
现在需要并行的执行一组任务,每一个任务都需要分阶段完成,并且任务不能抢先执行,当在某个阶段完成的时间过早时,需要等待其它任务全部完成这个阶段的工作后,才能一起向下一个阶段跃进。它与CountDownLatch很类似,只不过CountDownLatch只能触发一次(控制一次任务的阶段暂停),而CyclicBarrier如其名,可以触发多次。
二. 从代码层面看看CyclicBarrier如何使用
下面举一个赛马的例子。现在有7匹马准备赛跑,与往常不同的是,一次只允许一匹马奔跑(比如跑1秒,能跑多远就看马的实力了),跑完后就会被栅栏给拦住,停下来让下一匹马跑,以此类推,当且仅当所有的马都跑了1秒后,本轮奔跑才算结束,所有的马准备开始下一轮的奔跑。
Java为了仿真上述的场景,创建了马儿这个类,并且拥有"奔跑"这个行为。
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;
}
public synchronized int getStrides() {
return strides;
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
synchronized (this) {
int temp = rand.nextInt(3);
//System.out.println("temp: " + temp);
strides += temp;
}
// 每一匹马都不会抢跑,当跑完一轮栅栏后,马儿会停下来,直到其它所有的马都跑完这轮栅栏后,才会一起向下一个栅栏移动。
barrier.await();
}
} catch (InterruptedException e) {
// 使用合适的方式退出
System.out.println("Horse interrupt");
} catch (BrokenBarrierException e) {
// 我们需要关注的异常
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return "Horse " + id + " ";
}
/**
* 足迹
*/
public String tracks() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < getStrides(); i++) {
s.append("*");
}
s.append(id);
return s.toString();
}
}
public class HorseRace {
static final int FINISH_LINE = 75;
private List<Horse> horses = new ArrayList<>();
private ExecutorService exec = Executors.newCachedThreadPool();
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause) {
barrier = new CyclicBarrier(nHorses, new Runnable() {
@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(pause);
}catch (InterruptedException e) {
System.out.println("barrier-action sleep interrupted");
}
}
});
for (int i = 0; i < nHorses; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
public static void main(String[] args) {
int nHorse = 7;
int pause = 200;
if (args.length > 0) {
int n = new Integer(args[0]);
nHorse = n > 0 ? n : nHorse;
}
if (args.length > 1) {
int p = new Integer(args[1]);
pause = p > -1 ? p : pause;
}
new HorseRace(nHorse, pause);
}
}
HorseRace的构造函数中初始化了CyclicBarrier,nHorses的数量是"计数器",每一个线程都必须等待其它的线程本轮执行完毕(监控的手段是检查barrier的计数器的值是否为0),每次执行barrier.await()都会释放一次计数器(数值减一),当且仅当计数器的值为0时,才会执行Runnable(把它看做"栅栏动作"),栅栏动作中,绘画出了绘画出每匹马在本轮的赛跑足迹。每匹马在跑完一轮后,都会执行CyclicBarrier对象的await( )成员方法,暂停当前线程(释放计数器的值)。
从例子中抽象一层,可以得出以下场景:
有多个任务并行运行,每个任务内部都在进行循环,每一轮循环的末尾都写上了cyclicBarrier.await()
当且仅当在这一轮中所有的任务都执行了await()后,计数器的值为0,才会开始运行cyclicBarrier Runnable()内的信息,当Runnable也运行完毕后,宣告本轮所有任务运行完毕,接着一起进入下一轮任务的执行。
值得注意的是,创建多匹马后,每匹马每轮奔跑的步长不同。这是因为所有的马共用了同一个Random对象,每次执行nextInt()后,random的底层会执行(oldseed * multiplier + addend) & mask 重新计算种子,因此nextInt()的值也会发生改变。如果将Random对象的static去掉,所有的马一定会同时到达终点。
再给一个网上看到的较为简单的例子:
public class CyclicBarrierDemo {
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
}
执行结果:
Thread-1 到达栅栏 A
Thread-3 到达栅栏 A
Thread-0 到达栅栏 A
Thread-4 到达栅栏 A
Thread-2 到达栅栏 A
Thread-2 完成最后任务
Thread-2 冲破栅栏 A
Thread-1 冲破栅栏 A
Thread-3 冲破栅栏 A
Thread-4 冲破栅栏 A
Thread-0 冲破栅栏 A
Thread-4 到达栅栏 B
Thread-0 到达栅栏 B
Thread-3 到达栅栏 B
Thread-2 到达栅栏 B
Thread-1 到达栅栏 B
Thread-1 完成最后任务
Thread-1 冲破栅栏 B
Thread-0 冲破栅栏 B
Thread-4 冲破栅栏 B
Thread-2 冲破栅栏 B
Thread-3 冲破栅栏 B
此外,如果没有对每一轮执行后有代码运行的需求,我们还可以使用CyclicBarrier barrier = new CyclicBarrier(int parties);
parties: 开启屏障前,执行线程的个数。 也就是计数器个数。