CyclicBarrier

170 阅读3分钟

CyclicBarrier支持所有线程互相等待直到所有线程都到达一个屏障点。

CyclicBarrier的入参是1个数字,标识CyclicBarrier屏障关闭需要被几个线程调用await方法。还有1个可选参数,是Runnable类型,标识屏障被打开时被执行的行为。

CyclicBarrier(int parties, Runnable barrierAction)

CyclicBarrier(int parties)

CyclicBarrier最重要的方法是await方法。

我们先进到await方法,发现调用了dowait方法,关键的逻辑也在dowait里面。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

为了方便描述,我们默认屏障默认是打开的,所有线程调用await方法后屏障是关闭的。

我们把CyclicBarrier的dowait方法分为4部分

  • 前置校验
  • 屏障关闭处理逻辑
  • 屏障没有关闭处理逻辑
  • 后置处理

前置校验

首先,给dowait方法加锁。

然后获取1个Generation对象,CyclicBarrier是个循环屏障,每次屏障被打开,或者被reset,会生成1个新的Generation对象,标识当前屏障的时代。Generation只有1个变量broken,表示当前时代的屏障是否被打破。获取到当前时代之后,判断是否被打破了,如果被打破了,就抛1个异常。

然后判断当前线程是否被中断,如果被中断,就打破当前屏障。

final ReentrantLock lock = this.lock;
lock.lock();
final Generation g = generation;
if (g.broken)
    throw new BrokenBarrierException();
if (Thread.interrupted()) {
    breakBarrier();
    throw new InterruptedException();
}

打开breakBarrier方法,发现把时代里的是否打破屏障的标识改为true,然后把等待打开屏障条件的线程全部唤醒。

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

屏障关闭处理逻辑

count变量表示离打开屏障还需要被调用多少次。

这里减1之后,如果判断等于0,则表示屏障可以打开。

这里先获取需要被回调的Runnable实现,如果不是空,就执行实现。

执行之后把时代更新为下1个时代。

在finally代码块里,判断如果ranAction是false,就打破屏障。这里是为了防止Runnable实现有问题导致所有线程一直阻塞,同时认为这个屏障不应该再被使用。

int index = --count;
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();
    }
}

我们看下是如果更新时代的。

现唤醒所有等待打开屏障条件的线程,然后重制count的值变为parties(构造函数入参),然后创建下个时代对象。

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

屏障没有关闭处理逻辑

将线程在等待屏障关闭事件等待,这里有不带限时时间和带限时事件2种实现方式。

如果线程被中断,就判断时代是否改变,如果没有改变而且屏障没有打破,那就打破屏障抛出异常。否则就将线程再次中断。

如果屏障被打破,就抛出异常。

如果时代更改,正常返回。

如果超时,打破屏障,抛出异常。

// loop until tripped, broken, interrupted, or timed outfor (;;) {
    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();
    }
}

这些逻辑都处理完之后,进行解锁。

总结

  • await操作基本上都在同步执行
  • 回调实现有问题、线程中断、超时都会打破屏障
  • 屏障关闭、reset会更新时代