你对CyclicBarrier(循环栅栏)的了解有多少

目录:

1 CyclicBarrier 的应用场景
2 CyclicBarrier 的使用示例
3 CyclicBarrier 源码分析
4 CyclicBarrier 和 CountDownLatch 的区别

前言:

CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。

CountDownLatch的实现是基于AQS的,而CycliBarrier是基于 ReentrantLock(ReentrantLock也属于AQS同步器)和 Condition 的.

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties) ,其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

再来看一下它的构造函数:

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;
}

其中,parties 就代表了有拦截的线程的数量,当拦截的线程数量达到这个值的时候就打开栅栏,让所有线程通过。

内容:

1 CyclicBarrier 的应用场景

CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个 Excel 保存了用户所有银行流水,每个 Sheet 保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个 sheet 里的银行流水,都执行完之后,得到每个 sheet 的日均银行流水,最后,再用 barrierAction 用这些线程的计算结果,计算出整个 Excel 的日均银行流水。

2 CyclicBarrier 的使用示例

示例 1:

/**
*
* @author Snailclimb
* @date 2018年10月1日
* @Description: 测试 CyclicBarrier 类中带参数的 await() 方法
*/
public class CyclicBarrierExample2 {
 // 请求的数量
 private static final int threadCount = 550;
 // 需要同步的线程数量
 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
 public static void main(String[] args) throws InterruptedException {
  // 创建线程池
  ExecutorService threadPool = Executors.newFixedThreadPool(10);
  for (int i = 0; i < threadCount; i++) {
   final int threadNum = i;
   Thread.sleep(1000);
   threadPool.execute(() -> {
    try {
     test(threadNum);
   } catch (InterruptedException e) {
     // TODO Auto-generated catch block
 e.printStackTrace();
   } catch (BrokenBarrierException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }
  });
 }
  threadPool.shutdown();
}
 public static void test(int threadnum) throws InterruptedException,
BrokenBarrierException {
  System.out.println("threadnum:" + threadnum + "is ready");
  try {
   /**等待60秒,保证子线程完全执行结束*/
   cyclicBarrier.await(60, TimeUnit.SECONDS);
 } catch (Exception e) {
   System.out.println("-----CyclicBarrierException------");
 }
  System.out.println("threadnum:" + threadnum + "is finish");
}
}

运行结果,如下:

threadnum:0is ready
threadnum:1is ready
threadnum:2is ready
threadnum:3is ready
threadnum:4is ready
threadnum:4is finish
threadnum:0is finish
threadnum:1is finish
threadnum:2is finish
threadnum:3is finish
threadnum:5is ready
threadnum:6is ready
threadnum:7is ready
threadnum:8is ready
threadnum:9is ready
threadnum:9is finish
threadnum:5is finish
threadnum:8is finish
threadnum:7is finish
threadnum:6is finish

......

可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, await 方法之后的方法才被执行。另外,CyclicBarrier 还提供一个更高级的构造函数 CyclicBarrier(int parties, RunnablebarrierAction) ,用于在线程到达屏障时,优先执行 barrierAction ,方便处理更复杂的业务场景。示例代码如下:

/**
*
* @author SnailClimb
* @date 2018年10月1日
* @Description: 新建 CyclicBarrier 的时候指定一个 Runnable
*/
public class CyclicBarrierExample3 {
 // 请求的数量
 private static final int threadCount = 550;
 // 需要同步的线程数量
 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () ->
{
  System.out.println("------当线程数达到之后,优先执行------");
});
 public static void main(String[] args) throws InterruptedException {
  // 创建线程池
  ExecutorService threadPool = Executors.newFixedThreadPool(10);
  for (int i = 0; i < threadCount; i++) {
   final int threadNum = i;
   Thread.sleep(1000);
   threadPool.execute(() -> {
    try {
     test(threadNum);
   } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   } catch (BrokenBarrierException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }
  });
 }
  threadPool.shutdown();
}
 public static void test(int threadnum) throws InterruptedException,
BrokenBarrierException {
  System.out.println("threadnum:" + threadnum + "is ready");
  cyclicBarrier.await();
  System.out.println("threadnum:" + threadnum + "is finish");
}
}

运行结果,如下:

threadnum:0is ready
threadnum:1is ready
threadnum:2is ready
threadnum:3is ready
threadnum:4is ready
------当线程数达到之后,优先执行------
threadnum:4is finish
threadnum:0is finish
threadnum:2is finish
threadnum:1is finish
threadnum:3is finish
threadnum:5is ready
threadnum:6is ready
threadnum:7is ready
threadnum:8is ready
threadnum:9is ready
------当线程数达到之后,优先执行------
threadnum:9is finish
threadnum:5is finish
threadnum:6is finish
threadnum:8is finish
threadnum:7is finish
......

3 CyclicBarrier 源码分析

当调用 CyclicBarrier 对象调用 await() 方法时,实际上调用的是 dowait(false, 0L) 方法。await() 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。

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

dowait(false, 0L) :
// 当线程数量或者请求数量达到 count 时 await 之后的方法才会被执行。上面的示例中 count
的值就为 5。

private int count;
  /**
  * Main barrier code, covering the various policies.
  */
  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();
     }
      // cout减1
      int index = --count;
      // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行
await 方法之后的条件
 if (index == 0) {  // tripped
        boolean ranAction = false;
        try {
          final Runnable command = barrierCommand;
          if (command != null)
            command.run();
          ranAction = true;
          // 将 count 重置为 parties 属性的初始化值
          // 唤醒之前等待的线程
          // 下一波执行开始
          nextGeneration();
          return 0;
       } finally {
          if (!ranAction)
            breakBarrier();
       }
     }
     // loop until tripped, broken, interrupted, or timed out
      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 {
            // 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;
        if (timed && nanos <= 0L) {
          breakBarrier();
          throw new TimeoutException();
       }
     }
   } finally {
      lock.unlock();
   }
 }

总结: CyclicBarrier 内部通过一个 count 变量作为计数器,cout 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。

4 CyclicBarrier 和 CountDownLatch 的区别

下面这个是国外一个大佬的回答:

CountDownLatch 是计数器,只能使用一次,而 CyclicBarrier 的计数器提供 reset 功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从 jdk 作者设计的目的来看,javadoc 是这么描述它们的:

CountDownLatch: A synchronization aid that allows one or more threads to wait until a setof operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;)

CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other toreach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。)

对于 CountDownLatch 来说,重点是“一个线程(多个线程)等待”,而其他的 N 个线程在完成“某件事情”之后,可以终止,也可以等待。而对于 CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

CountDownLatch 是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier 更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

以上就是本期更新内容,关注下期更精彩!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值