一. 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: 开启屏障前,执行线程的个数。 也就是计数器个数。