解析阿里一面CyclicBarrier和CountDownLatch的区别

引言

前面一篇文章我们《Java线程并发工具类CountDownLatch原理及用法》它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0的时候,如果 再有线程调用去 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用。为了解决这个问题CyclicBarrier就应运而生了。

什么是CyclicBarrier

CyclicBarrier是什么?把它拆开来翻译就是循环(Cycle)和屏障(Barrier

它的主要作用其实和CountDownLanch差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,不过它是可以循环执行的,这是它与CountDownLanch最大的不同。CountDownLanch是只有当最后一个线程把计数器置为0的时候,其他阻塞的线程才会继续执行。学习CyclicBarrier之前建议先去看看这几篇文章:

《Java高并发编程基础之AQS》

《Java高并发编程基础三大利器之Semaphore》

《Java高并发编程基础三大利器之CountDownLatch》

如何使用

我们首先先来看下关于使用CyclicBarrier的一个demo:比如游戏中有个关卡的时候,每次进入下一关的时候都需要进行加载一些地图、特效背景音乐什么的只有全部加载完了才能够进行游戏:

public class CyclicBarrierExample {
 static class PreTaskThread implements Runnable {
 private String task;
 private CyclicBarrier cyclicBarrier;

 public PreTaskThread(String task, CyclicBarrier cyclicBarrier) {
  this.task = task;
  this.cyclicBarrier = cyclicBarrier;
 }

 @Override
 public void run() {
  for (int i = 0; i < 4; i++) {
  Random random = new Random();
  try {
   Thread.sleep(random.nextInt(1000));
   System.out.println(String.format("关卡 %d 的任务 %s 完成", i, task));
   cyclicBarrier.await();
  } catch (InterruptedException | BrokenBarrierException e) {
   e.printStackTrace();
  }
  }
 }

 public static void main(String[] args) {
  CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
  System.out.println("本关卡所有的前置任务完成,开始游戏... ...");
  });
  new Thread(new PreTaskThread("加载地图数据", cyclicBarrier)).start();
  new Thread(new PreTaskThread("加载人物模型", cyclicBarrier)).start();
  new Thread(new PreTaskThread("加载背景音乐", cyclicBarrier)).start();
 }
 }
}

输出结果如下:

我们可以看到每次游戏开始都会等当前关卡把游戏的人物模型,地图数据、背景音乐加载完成后才会开始进行游戏。并且还是可以循环控制的。

源码分析

结构组成

 /** The lock for guarding barrier entry */
 private final ReentrantLock lock = new ReentrantLock();
 /** Condition to wait on until tripped */
 private final Condition trip = lock.newCondition();
 /** The number of parties */
 private final int parties;
 /* The command to run when tripped */
 private final Runnable barrierCommand;
 /** The current generation */
 private Generation generation = new Generation();
  • lock:用于保护屏障入口的锁
  • trip :达到屏障并且不能放行的线程在trip条件变量上等待
  • parties :栅栏开启需要的到达线程总数barrierCommand:最后一个线程到达屏障后执行的回调任务
  • generation:这是一个内部类,通过它实现CyclicBarrier重复利用,每当await达到最大次数的时候,就会重新new 一个,表示进入了下一个轮回。里面只有一个boolean型属性,用来表示当前轮回是否有线程中断。

主要方法

await方法

 public int await() throws InterruptedException, BrokenBarrierException {
 try {
  return dowait(false, 0L);
 } catch (TimeoutException toe) {
  throw new Error(toe); // cannot happen
 }
 }
 /**
 * 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 {
  //获取barrier当前的 “代”也就是当前循环
  final Generation g = generation;
  if (g.broken)
  throw new BrokenBarrierException();

  if (Thread.interrupted()) {
  breakBarrier();
  throw new InterruptedException();
  }
  // 每来一个线程调用await方法都会进行减1
  int index = --count;
  if (index == 0) { // tripped
  boolean ranAction = false;
  try {
   final Runnable command = barrierCommand;
   // new CyclicBarrier 传入 的barrierCommand, command.run()这个方法是同步的,如果耗时比较多的话,是否执行的时候需要考虑下是否异步来执行。
   if (command != null)
   command.run();
   ranAction = true;
   // 这个方法1. 唤醒所有阻塞的线程,2. 重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
   nextGeneration();
   return 0;
  } finally {
   if (!ranAction)
   breakBarrier();
  }
  }

  // loop until tripped, broken, interrupted, or timed out
  for (;;) {
  try {
   // 进入if条件,说明是不带超时的await
   if (!timed)
    // 当前线程会释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
   trip.await();
   else if (nanos > 0L)
    //说明当前线程调用await方法时 是指定了 超时时间的!
   nanos = trip.awaitNanos(nanos);
  } catch (InterruptedException ie) {
   //Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
   //g == generation 成立,说明当前代并没有变化。
   //! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
   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.
   //执行到else有几种情况?
   //1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
   //2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出 brokenBarrier异常。也记录下中断标记位。
   Thread.currentThread().interrupt();
   }
  }
  //唤醒后,执行到这里,有几种情况?
  //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
  //2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
  //3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
  if (g.broken)
   throw new BrokenBarrierException();
  //唤醒后,执行到这里,有几种情况?
  //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
  //2.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
  if (g != generation)
   return index;
  //唤醒后,执行到这里,有几种情况?
  //.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
  if (timed && nanos <= 0L) {
   breakBarrier();
   throw new TimeoutException();
  }
  }
 } finally {
  lock.unlock();
 }
 }

小结

到了这里我们是不是可以知道为啥CyclicBarrier可以进行循环计数?
CyclicBarrier采用一个内部类Generation来维护当前循环,每一个await方法都会存储当前的generation,获取到相同generation对象的属于同一组,每当count的次数耗尽就会重新new一个Generation并且重新设置count的值为parties,表示进入下一次新的循环。
从这个await方法我们是不是可以知道只要有一个线程被中断了,当代的 generationbroken 就会被设置为true,所以会导致其他的线程也会被抛出BrokenBarrierException。相当于一个失败其他也必须失败,感觉有“强一致性“的味道。

总结

CountDownLanch是为计数器是设置一个值,当多次执行countdown后,计数器减为0的时候所有线程被唤醒,然后CountDownLanch失效,只能够使用一次。

CyclicBarrier是当count0时同样唤醒全部线程,同时会重新设置countparties,重新new一个generation来实现重复利用。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。感
  • 谢您的阅读,十分欢迎并感谢您的关注。

巨人的肩膀摘苹果

https://javajr.cn/
http://www.360doc.com/content/20/0812/08/55930996_929792021.shtml
https://www.cnblogs.com/xxyyy/p/12958160.html

到此这篇关于阿里一面CyclicBarrier和CountDownLatch的区别是啥的文章就介绍到这了,更多相关CyclicBarrier和CountDownLatch的区别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • spring boot 实现阿里云视频点播功能(删除视频)

    目录: 1.spring boot实现阿里云视频点播上传视频(复制粘贴即可) 2.spring boot 实现阿里云视频点播 --删除视频 导包和部分类在spring boot实现阿里云视频点播上传视频(复制粘贴即可)博客有说明,就不再重复了. InitVodCilent public class InitVodCilent { public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySec

  • spring boot实现阿里云视频点播上传视频功能(复制粘贴即可)

    目录: 1.spring boot实现阿里云视频点播上传视频(复制粘贴即可) 2.spring boot 实现阿里云视频点播 --删除视频 准备工作: 阿里云地址: https://www.aliyun.com/ 登录阿里云,确保账户有余额,有几毛钱就够了,开发学习基本不花钱. 2.进入阿里云开通视频点播功能 3.获取阿里云密钥 代码编写: 目录结构: application.properties # 服务端口 server.port=8003 # 服务名 spring.application.

  • 解析阿里一面CyclicBarrier和CountDownLatch的区别

    引言 前面一篇文章我们<Java线程并发工具类CountDownLatch原理及用法>它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0的时候,如果 再有线程调用去 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用.为了解决这个问题CyclicBarrier就应运而生了. 什么是CyclicBarrier CyclicBarrier是什么?把它拆开来翻译就是循环(Cycle)和屏障(Barrier) 它的主要作用其实和Cou

  • 基于CyclicBarrier和CountDownLatch的使用区别说明

    2018.12.12更新 在学习了CyclicBarrier之后发现,CyclicBarrier也可以实现跟CountDownLatch类似的功能,只需要在它的parties中多设置一个数,将主线程加入等待队列就可以了: public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); int size = 3; // 设置参数时,线程实际执行数size+1,将main

  • Java中CyclicBarrier和CountDownLatch的用法与区别

    目录 前言 CountDownLatch 例子 CyclicBarrier 构造函数 例子 两者区别 前言 CyclicBarrier和CountDownLatch这两个工具都是在java.util.concurrent包下,并且平时很多场景都会使用到. 本文将会对两者进行分析,记录他们的用法和区别. CountDownLatch CountDownLatch是一个非常实用的多线程控制工具类,称之为"倒计时器",它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行. Coun

  • 解析阿里云centos7服务器nginx配置及常见问题解答

    前言: 本文参考了jackyzm的博客:https://www.cnblogs.com/jackyzm/p/9600738.html,进行了内容的更新,并请注意这里适用的版本是centos7的版本.并且本文的配置方式曾经在版本8上失败过,因此查看本文前最好先确定服务器的版本. 而关于nginx部分问题的处理,则是参考了文章:http://www.mamicode.com/info-detail-3008792.html进行,其中包括的部分错误如下: 1.make[1]: *** [objs/Ma

  • 解析阿里GTS开源版本fescar分布式事务

    目录 前言碎语 什么是FESCAR? 微服务中的分布式事务问题 FESCAR怎么做? FESACR有3个基本组件: FESCAR管理分布式事务的典型生命周期: 历史 前言碎语 阿里重磅开源fescar分布式事务框架.其前身是拥有多项专利的云分布式事务产品GTS.很早前阿里在推广GTS分布式事务的时候就隐隐透露过可能会有开源项目的推出,终于在社区千呼万唤之下fescar发布了.目前是0.1的版本,因为脱胎于商业产品,社区版本要上生产环境可能需要在社区迭代孵化一段时间.代码可以先拉下来研究一下,后期

  • 解析React中useMemo与useCallback的区别

    useMemo 把“创建”函数和依赖项数组作为参数传⼊入useMemo,它仅会在某个依赖项改变时才重新计算memoized 值.这种优化有助于避免在每次渲染时都进⾏行行⾼高开销的计算. importReact, { useState, useMemo } from"react"; export default functionUseMemoPage(props) { const [count, setCount] =useState(0); constexpensive=useMemo

  • 解析php函数method_exists()与is_callable()的区别

    php函数method_exists() 与is_callable()的区别在哪?在php面相对象设计过程中,往往我们需要在调用某一个方法是否属于某一个类的时候做出判断,常用的方法有 method_exists()和is_callable(),相比之下,is_callable()函数要高级一些,它接受字符串变量形式的方法名作为 第一个参数,如果类方法存在并且可以调用,则返回true.如果要检测类中的方法是否能被调用,可以给函数传递一个数组而不是类的方法名作为参数.数组必须包含对象或类名,以将其作

  • 解析php中mysql_connect与mysql_pconncet的区别详解

    说说mysql_connect与mysql_pconnect的区别,这俩函数用法上差不多,网上有说应该用pconnect的,pconnect是个 好东西:也有视pconnect如洪水猛兽的,坚决不让用pconnect的,也有态度暧昧不清的.那这个东西到底如何呢? 永久链接并不是说,服务器打开了一个连接,然后所有的人都共享这个链接.永久连接一样是每个客户端来就打开一个连接,有200人访问就有200个连接.其 实mysql_pconnect()本身并没有做太多的处理, 它唯一做的只是在php运行结束

  • 详细解析Java中抽象类和接口的区别

    在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制.正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力.abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进 行抽象类定义时对于abstract class和interface的选择显得比较随意.其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对 于问题领域本质的理解.对于设计意图的理解是否正确.合理

  • 通过实例解析java过滤器和拦截器的区别

    区别 1.使用范围和规范不同 filter是servlet规范规定的,只能用在web程序中. 拦截器即可以用在web程序中, 也可以用于application, swing程序中, 是Spring容器内的, 是Spring框架支持的 2.触发时机不同 顺序: Filter-->Servlet-->Interceptor-->Controller 过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的.请求结束返回也是,是在servlet处理完后,返回给前端之前过滤器处理. 拦

随机推荐