Java并发编程系列:CyclicBarrier

CyclicBarrier简介

CyclicBarrier也是基于ReentrantLock和Condition的一个同步工具类,它的作用是让一些线程到达某个公共屏障点时,等待未到达的线程。当所有线程到达屏障点时,继续往下执行。

先看一个例子

public class CyclicBarrierDemo implements Runnable {

    private CyclicBarrier cyclicBarrier;

    public CyclicBarrierDemo(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " running ");
        try {
            cyclicBarrier.await();
            System.out.println("all arrive at " + System.currentTimeMillis());
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) throws IOException {
        CyclicBarrier barrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            new Thread(new CyclicBarrierDemo(barrier)).start();
        }
        System.in.read();
    }
}

实现原理

和其他同步工具不同的是,它的内部并没有继承AQS的内部类Sync。

private static class Generation {
	// 当前的屏障是否重置,为true的话,说明这个屏障已经损坏,当某个线程await的时候,直接抛出异常
    boolean broken = false;
}

//同步操作锁
private final ReentrantLock lock = new ReentrantLock();
//线程拦截器
private final Condition trip = lock.newCondition();
// 等待的线程数
private final int parties;
// 所有线程到达后执行的任务
private final Runnable barrierCommand;
// 屏障代
private Generation generation = new Generation();
// 还能阻塞的线程数
private int count;
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

# await

CyclicBarrier最重要的方法就是await了,它的作用在例子中已经体会到了,下面进入代码看看它到底是如何实现的。

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
        int index = --count;
        if (index == 0) {  // count减为0,那么需要唤醒所有的线程,并且这个将屏障重置
            boolean ranAction = false;
            try {// 执行任务
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();// 
                return 0;
            } finally {
                if (!ranAction)// 任务没能成功执行,则重置屏障
                    breakBarrier();
            }
        }
        for (;;) {// 如果计数器不为0则执行此循环
            try {
                if (!timed)// 如果非定时等待,调用condition.wait()
                    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();
    }
}

逻辑较长总结一下:

  1. 如果当前线程不是最后一个线程,也就是说count不为0时,根据调用的await是否为限时等待,调用condition的await方法,此时线程释放锁,进入Condition的等待队列(AQS那篇文章中讲过),当它被唤醒时需要判断当前屏障是否被破坏,并且判断屏障是否换代。

总结一下什么时候会破坏屏障:

a. 某一个线程被中断
b. 屏障任务执行失败
c. 屏障换代
d. 等待时间设置小于0
e. 调用reset方法重置

什么时候会换代:
a. 当所有线程都到达屏障,并且任务成功执行
b. 调用reset方法
2. 如果当前线程是最后一个到达屏障的线程,那么先执行任务,然后对屏障换代,执行nextGeneration方法。

private void nextGeneration() {
    // 唤醒所有阻塞线程
    trip.signalAll();
    // 重置线程数
    count = parties;
    // 新建generation 
    generation = new Generation();
}
private void breakBarrier() {
  generation.broken = true;
  //设置计数器的值为需要拦截的线程数
  count = parties;
  //唤醒所有线程
  trip.signalAll();
}

至此CyclicBarrier已经讲述完毕,最后一点CyclicBarrier可以复用,即当屏障使用完一次之后,自动的重置线程数。但是CountDownLach则不会重置计数器。

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