并发工具类源码分析---CyclicBarrier(详细)

一、作用

  •   JDK1.8英文注释
* A synchronization aid that allows a set of threads to all wait for
* each other to reach a common barrier point.  CyclicBarriers are
* useful in programs involving a fixed sized party of threads that
* must occasionally wait for each other. The barrier is called
* <em>cyclic</em> because it can be re-used after the waiting threads
* are released.
  • 翻译

其作用在于使一组线程相互等待直到到达某个共同点(类似于栅栏)。循环的意思为这个栅栏可以被重复使用(即第一次等待所有线程到达共同点后,会重新初始化),后文会介绍。

二、实现分析

 

  • 问题一:多个线程相互等待?

在满足什么情况下等待(等待条件),如何等待(已知有Object.wait(),Condition.await()方法)。

  • 问题二:等待的线程何时被唤醒(共同点是什么)?

在满足什么情况下唤醒(唤醒条件),如何唤醒(已知有Object.notifyAll(),Codition.signalAll()方法)。

  • 问题三:如何实现重复使用栅栏?

在满足什么情况下重新初始化栅栏(初始化条件),如何初始化(源码采用Generation实现,请看后文)。

三、基本思想

假设N个线程相互等待,初始化count=N,每个线程判断如果N!=0则进行等待并将N=N-1,如果某个线程判断N=0,则唤醒所有等待的线程,并重新更新Barrier使之可以重复使用。

四、源码分析

  • 属性
/** The lock for guarding barrier entry */
//作用一:产生Condition对象  作用二:同步await()方法。
private final ReentrantLock lock = new ReentrantLock();

/** Condition to wait on until tripped */
//问题一问题二中等待唤醒采用Condition.await()/awaitNanos(long)和Condition.signalAll()方法。
private final Condition trip = lock.newCondition();

/** The number of parties */
//循换栅栏可以重复使用,那么当再次进行初始化的时候,根据这个值进行初始化等待线程的个数(只是构造函数传参的一个副本,不随着await()方法的调用而递减,相反的是count属性)。
private final int parties;

/* The command to run when tripped */
//栅栏提供的额外功能,即当所有线程达到共同点后,由最后一个(count=0)达到的线程执行这个命令(先执行这个命令然后再唤醒其他线程),可以想一下使用场景。
private final Runnable barrierCommand;

/** The current generation */
//实现循环栅栏,表示当前栅栏所属年代,当所有线程到达共同点后会用nextGeneration对栅栏进行重新初始化并更新年代。
private Generation generation = new Generation();

/**
 * Number of parties still waiting. Counts down from parties to 0
 * on each generation.  It is reset to parties on each new
 * generation or when broken.
 */
//表示还有多少线程在进行等待,每当一个线程调用await()方法之后,会减一,当为0时唤醒其他等待线程。
private int count;
  • 构造函数
public CyclicBarrier(int parties) {
    this(parties, null);
}
//初始化以上解释的几个参数。
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}
  • 方法

//进行线程的等待,唤醒操作,返回剩余等待的线程数。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        //主要调用dowait方法
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
//timied:是否设置了等待超时 nanos:如果设置了超时,超时时间,纳秒为单位
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    //这里同步的目的有如下两个原因
    //一:调用Condition.await()方法之前,该线程必须持有锁,否则抛出IllegalMonitorStateException。
    //二:需要对count进行减一操作,多线程情况下要么用CAS,要么同步,在原因一的基础上这里用同步一并解决。
    lock.lock();
    try {
        //保存当前栅栏的年代,下文会用到
        final Generation g = generation;
        
       //当前栅栏处于broken状态,可以通过源码查看如果存在以下情况,则设置Generation .broken=true
       //一:当某一个线程被中断;栅栏中如果某一线程被中断,那么其他等待的线程则会全部抛出BrokenBarrierException。
       //二:执行传入的Runnable任务时抛出异常。
       //三:调用Barrier.reset()方法。
       //四:某个线程调用了await(long,TimeUnit)方法,且达到超时时间。
        if (g.broken)
            throw new BrokenBarrierException();
        //当前线程被中断,则销毁当前栅栏
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        //线程调用await()方法只有,对应的count=count-1,因为用了同步,所以线程安全。
        int index = --count;
        //问题二的共同点
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                //所有线程到达共同点时,由最后一个线程(count=0)执行命令
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
               //执行完命令设为true,如果执行命令中抛出异常,那么依然为false。
                ranAction = true;
               //当执行完命令之后,唤醒所有等待线程,更新Barrier的generation,重新初始化barrier。
                nextGeneration();
                return 0;
            } finally {
                //当执行command命令抛出异常时,销毁当前barrier。
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                //经历以上步骤,如果还没有到达共同点,则让当前线程在Condition上等待。
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                   //返回的nanos代表{@code nanosTimeout}值的估计值减去从该方法返回时等待的时间。一个正值可以用作对该方法的后续调用
                   //的参数,以完成所 需的等待时间。小于或等于零的值表示时间所剩无几。
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                //线程被中断,且还没有达到共同点(因为达到共同点之后g!=generation),如果barrier没有被销毁,则进行销毁。
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    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.
                    //暂时未理解,后续补充...
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();
            //如果还未到达共同点,返回剩余等待的线程个数。
            if (g != generation)
                return index;
          //如果设置了等待,且等待时间到达(看上面awaitNanos解释),则销毁栅栏。否则继续for循环。
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
//销毁栅栏,之后,所有等待的线程都会抛出BarrierBrokenExcepition

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}
//当到达共同点后,唤醒所有等待线程并重新初始化栅栏,产生新的generation。
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

以上就是CyclicBarrier源码的分析,其主要是借用了ReentrantLock和Condition实现线程的协作(等待和唤醒),并通过count控制栅栏的共同点。通过到达共同点后,刷新栅栏来达到重用的目的。

-------------------------------------------------------------------------------------------------------------------

知其然,更要知其所以然...

-------------------------------------------------------------------------------------------------------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章