CyclicBarrier 解析

CyclicBarrier 解析

1. 属性分析

//
private final ReentrantLock lock = new ReentrantLock();
// 等待触发的集合
private final Condition trip = lock.newCondition();
// Barrier数量
private final int parties;
// 触发之后的回调(也就是触发之后的动作)
private final Runnable barrierCommand;
// 表示一代线程
private Generation generation = new Generation();

// 还在等待的线程数量,一开始的时候,count等于parties,之后,只有有一个线程await,这个count就是--。
private int count;

2. 构造函数

构造函数没有什么说的,标准的赋值操作,需要注意,这里的parties保存的是最原始的值,count是会活动的。也就是说,每次只要有一个线程await了。那么,(一开始count等于parties)count就是–。count变为0的时候就会将count重新置为parties的值。

在这里插入图片描述

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties; // 数量,这就是等待的数量
    this.count = parties;      // 同样的,把等待的数量赋值给parties。
    this.barrierCommand = barrierAction; // 回调的command
}

3. 常用方法

1. await方法分析

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
dowait方法
//参数说明:
// timed:是否要超时等待
// nanos:等待时间
private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // 这不是基于aqs来实现的。而是通过ReentrantLock来实现的。
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
           // 拿到这一代线程的表示对象,用这个对象来表示满足count的线程集合。表示一代。
            final Generation g = generation;
           
           // 如果这一代需要中断。直接抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
            // 如果当前线程中断了,就调用breakBarrier。做清楚操作,并且抛出异常。并且唤醒之前等待的线程,表示这一代的线程都不行了// 唤醒的线程在await方法之后,会有一个判断,如果为true,直接抛出异常。本线程抛出InterruptedException
            if (Thread.interrupted()) { 
                breakBarrier();
                throw new InterruptedException();
            }
          
           // 每次都是 --count
            int index = --count;
            if (index == 0) {   // 如果count等于0,就要触发了
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null) // 运行command
                        command.run();
                   // 标识位,标识运行成功
                    ranAction = true;
                   // 生成一个新的Generation,并且唤醒所有调用了await的线程。。重新设置count为parties的值(也就是一开始的值).被唤醒的线程就会走下面的 if (g != generation) 的判断逻辑,因为引用关系不一样的 。说明上一代已经执行过了,唤醒的就赶紧返回把。               
                    nextGeneration();
                    return 0;
                } finally {
                   // 如果运行没有成功,
                    if (!ranAction)
                       //那只有一种情况了,在运行的时候发送了异常。发生了异常,这种情况在下面breakBarrier里面已经说了
                        breakBarrier();
                }
            }

          // 如果--之后,count没有变为0.就循环开始等待了
          for (;;) {
                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 {
                       // 到这里只有两种情况
                       // 1. 换代更新了。
                       // 2. broker正常
                       // 这两种情况都说明,任务已经快结束了,或者已经结束了,但是这个时候却发生了中断操作。只能中断当前操作了,这里需要返回结果吗?不需要。因为中断会打破等待操作。况且,这个返回结果没有意义
                        Thread.currentThread().interrupt();
                    }
                }
                 
             // 到这里就说明当前等待的线程被唤醒了。
            
            
             // 如果唤醒之后generation的broken。需要中断。直接抛出异常。
                if (g.broken)
                    throw new BrokenBarrierException();
        
           // 如果代不一样了,也就是说,上一代的command已经完成了(只有在command执行结束之后,才会更新换代,或者reset方法)
            // 那这肯定是属于上一代的线程,当前线程直接返回就好了。
                if (g != generation)
                    return index;
                  
            // 如果需要超时等待,并且nanos小于等于0,就报错。nanos在上面的await里面,指的是剩下的等待时间。
            // 这个是一个估算的。
                if (timed && nanos <= 0L) {
                   // 清楚操作,到这里就说明超时了。
                  // 这段代码是在for循环里面的。只有超时等待,会走到这里,如果第一次被唤醒。
                  // 还剩下时间。走到这里的判断。如果nanos小于等于0,就说明超时了。
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

breakBarrier方法

在异常情况下,做清楚操作。

会在下面的几个地方调用

  1. 一开始,当前线程没有等待前,判断线程中断的时候。

    表示当前线程被中断了,就会导致这一批线程的等待出现问题。所以,将generation的broken设置为true。

    但是要注意,这个方法里面没有创建新generation的操作,只是改了标志位,同一批的线程获取到锁,会在判断中断前面检查generation的broken,如果是true,直接抛出异常。

    ​ 总结下来就是:

    如果一批线程里面,还没有等待之前,被中断了。这一批线程的await操作都会发生异常。必须重新调用reset方法,复位。否则后序的等待都会失败

  2. command运行失败的时候。

    如果传递进来的command运行报错。就会调用。和上面是一样的结果。

    没有直接修改generation。所以,下一批的线程还是会报错,因为这里还只是修改了generation。并没有创建。

    之前park住的线程,还有没有等待的线程,都会检测到。从而报错,直到调用了reset方法。

  3. 等待的时候,被打断的时候。如果g == generation && ! g.broken

    判断引用关系,如果引用关系相等,说明还是同一批线程,如果broken为true。最起码说明不是自己线程出现了问题,肯定是同一批次的别的线程出现了问题,也不可能是command发生了问题。还是同样的处理逻辑。并且利用异常的方法跳出循环。

    后序唤醒的线程。还是会走上面的逻辑,判断broken。抛出异常。

  4. 等待超时

    等待超时就直接抛出异常。唤醒的线程也像上面一样,还是会继续抛出异常。

  5. 调用reset的时候

看这里的逻辑很清晰

将generation的broken变为true。重新设置parties。唤醒所有等待的线程。

问题?

  1. generation是什么?有什么意义?

    generation表示一代线程。也就是在没有触发之前,等待的线程,用generation来表示这一代线程。 里面的broken里面表示是否中断过。

  2. generation既然表示 一代线程的话。既然是标志位,用true,false不行吗?

    不行。要知道。 trip.signalAll();的唤醒也是只能唤醒一个线程,并且,获取锁的时候线程是有随机性的。如果有布尔值。就可能会导致上一次的generation会影响下一次的generation。所以,可以看到在dowait方法里面会用引用判断。

 private void breakBarrier() {
        // 将generation的broken变为true
        generation.broken = true;
       // 重新设置count
        count = parties;
       // 唤醒所有的等待的线程。
        trip.signalAll();
    }
Generation类

就一个简单的静态内部类。

private static class Generation {
    boolean broken = false;
}

2. isBroken

返回generation里面的broker的属性值,generation代表这一代的线程。broker代表的意思是这一代的线程是否被破坏。

public boolean isBroken() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return generation.broken;
    } finally {
        lock.unlock();
    }
}

3. reset

重新设置CyclicBarrier

调用的方法在上面和下面都有分析。具体看他们各自方法的内容。

 public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //
            breakBarrier();   
            // 
            nextGeneration(); 
        } finally {
            lock.unlock();
        }
    }

4. nextGeneration

生成新的Generation,重新设置parties,唤醒所有等待的线程。

private void nextGeneration() {
         // 唤醒所有的等待的线程
         trip.signalAll();
        // 重新设置count为parties
        count = parties;
      // 重新创建Generation。表示下一代的需要等待的线程。
        generation = new Generation();
    }

5. getNumberWaiting

返回已经等待的线程的数量

count表示还需要等待多少。

parties表示初始值

parties - count 表示已经等待的线程的数量。

public int getNumberWaiting() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return parties - count;
    } finally {
        lock.unlock();
    }
}

我感觉,对于这个理解的难点在于Generation。要知道线程获取锁和等待都是随机的。将Generation和线程的随机性联系在一起。并且结合代(一代线程,CyclicBarrier本事就是当指定数量的线程到达之后,command才可以触发。并且还能重复使用),。就比较好理解了

关于 CyclicBarrier就分析到这里了,如有不正确的地方,欢迎指出。谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值