JAVA并发编程JUC之CyclicBarrier源码分析

CyclicBarrier

基本解释:

顾名思义循环栅栏,用于将多个线程等待在某个位置,所有线程都准备好之后,全部同时执行;它的一个特点就是可以循环使用(Cyclic);

有点类似于跑步比赛,大家等待在起跑点,一声枪响,所有参赛者都开始跑。

使用举例:

class Solver {
  final int N;
  final float[][] data;
  final CyclicBarrier barrier;

  class Worker implements Runnable {
    int myRow;
    Worker(int row) { myRow = row; }
    public void run() {
      while (!done()) {
        processRow(myRow);

        try {
          barrier.await();
        } catch (InterruptedException ex) {
          return;
        } catch (BrokenBarrierException ex) {
          return;
        }
      }
    }
  }

  public Solver(float[][] matrix) {
    data = matrix;
    N = matrix.length;
    Runnable barrierAction =
      new Runnable() { public void run() { mergeRows(...); }};
    barrier = new CyclicBarrier(N, barrierAction);

    List<Thread> threads = new ArrayList<Thread>(N);
    for (int i = 0; i < N; i++) {
      Thread thread = new Thread(new Worker(i));
      threads.add(thread);
      thread.start();
    }

    // wait until done
    for (Thread thread : threads)
      thread.join();
  }
}

 

源码分析:

主要成员变量包含:

// 用于创建Condition的锁
private
final ReentrantLock lock = new ReentrantLock();

// 所有线程都等待在此condition上
private final Condition trip = lock.newCondition();
// 记录此barrier有多少个参与者
private final int parties;
// 保存打开栅栏时,采取的动作
private final Runnable barrierCommand;
// 保存此轮
private Generation generation = new Generation();
// 记录还需多少个线程到达此barrier才能打开, 已等待数 = parties - count;
private int count;

 

其中主要的方法包括:

// 创建一个CyclicBarrier,其中parties为多少个线程等待在这个barrier上之后,打开栅栏,进入下一次迭代
// barrierAction表示在打开栅栏之前需要做的事情,由最后一个到达栅栏的线程执行
public
CyclicBarrier(int parties, Runnable barrierAction);

// 同上一个,栅栏打开的时候不需要做其他事情,只需要释放线程就可以
public CyclicBarrier(int parties);

// 将一个线程等待在此barrier上
public int await() throws InterruptedException, BrokenBarrierException;

// 将一个线程等待在此barrier上,并且最多等待timeout的时间,若时间到则退出等待,并且此线程会打开栅栏
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException;

// 查看barrier是否已经打开
public boolean isBroken();

// 重置栅栏,会将已经等待在此栅栏上的线程全都释放掉
public void reset();

// 获得等待在此栅栏上的线程数
public int getNumberWaiting();

 最主要的方法为两个await方法,这两个await方法同时调用了dowait方法,dowait负责具体的阻塞线程。

下面分析dowait方法:

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) // barrier已经被打开了,不能继续使用,除非重置(reset)一下
            throw new BrokenBarrierException();

        if (Thread.interrupted()) { // 此线程被中断了,破坏栅栏使得其他线程可以执行,此线程则被抛出InterruptedException
            breakBarrier();
            throw new InterruptedException();
        }

        int index = --count; // 更新count,count为剩余等待数,index则表示我是倒数第几个来打此barrier上的,0表示最后一个,parties-1表示最后一个
        if (index == 0) {  // tripped 所有线程都已经达到
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run(); // 最后一个线程执行打开栅栏时需要的动作
                ranAction = true;
                nextGeneration(); // 进入下一个周期
                return 0;
            } finally {
          // 如果在上述期间发生了任何异常,则会破坏barrier,使得等待在此barrier上的线程得以执行
          // 这里我觉得主要是为了防止command.run中出现异常信息时的一种恢复机制,毕竟其他人写的这个command并不可靠
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
     // 循环等待,直至触发,打开,中断或者超时
        for (;;) {
            try {
                if (!timed) // 未指定超时时间
                    trip.await(); // 等待在condition上
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos); // 等待至超时,或被唤醒
            } catch (InterruptedException ie) { // 被中断了
                if (g == generation && ! g.broken) { // 若当前barrier还是进入等待时的那一轮,并且没有被打开
                    breakBarrier(); // 打开barrier
                    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.
            // 此barrier已经不是我等待之前的那个barrier了,我现在要中断我自己,这里有点迷糊,不清楚为什么不直接抛出 ie
                    Thread.currentThread().interrupt();
                }
            }
       // 我被唤醒了,看看barrier是否broken掉了,如果是被broken掉的,说明出现了一些异常
            if (g.broken)
                throw new BrokenBarrierException();
       // 我被唤醒了,但是此barrier已经不是我等待时的那个barrier了,我不管它,继续做我自己的事情
            if (g != generation)
                return index;
            // 我超时了,不能继续待在此barrier上了,那我要破坏此barrier以便让其他线程知道
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

 

breakBarrier和reset也是频繁使用的方法:

private void breakBarrier() {
    generation.broken = true; // 标记barrier已被破坏
    count = parties; // 还原count
    trip.signalAll(); // 唤醒已经等待在此barrier上的线程
}

 

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation 破坏上一轮
        nextGeneration(); // start a new generation 开始下一轮
    } finally {
        lock.unlock();
    }
}

 

private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

 

posted @ 2019-12-27 15:41  AutumnLight  阅读(218)  评论(0编辑  收藏  举报