首页 > 编程语言 >CyclicBarrier源码阅读

CyclicBarrier源码阅读

时间:2024-01-30 17:00:51浏览次数:32  
标签:barrier lock await 源码 线程 阅读 action CyclicBarrier

目录

本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习

本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,否则你会不知道我在说什么

简介

说起CyclicBarrier不得不提一下CountDownLatch,他们最明显的区别就是:

  • CountDownLatch:将线程分为执行者和等待者
  • CyclicBarrier:执行者和等待者都是同一批线程

如何理解呢,CountDownLatch大家都知道,设置一批工作线程,各自执行完任务就调用CountDownLatch.countDown,表示自己已经执行完任务,该工作线程就可以继续干别的事情了。而CyclicBarrier相当于在CountDownLatch.countDown之后设置了一道屏障,自己完成了任务后,还得等别的工作线程也完成了之后才能继续干别的事情。

实际上CountDownLatch就能简单实现一个CyclicBarrier的核心功能:

// 这两句由同一个线程执行
countDownLatch.countDown();
countDownLatch.await();

就是这么简单,在countDown后加个await就行,相当于执行者和等待者都是同一个线程罢了。在我看来CyclicBarrier只是将两条语句合成了一句:barrier.await()

当然,CyclicBarrier还支持了其他的更加完善的机制:

  • 复用:不同于CountDownLatch只能用一次,每次count减到0后就不能再加回来了。CyclicBarrier可以复用,支持多轮等待
  • barrier action:创建CyclicBarrier时可以传入一个Runnable作为barrier action,在最后一个线程到达(await)时,由该线程执行该barrier action,执行完毕之后,所有线程才能被唤醒继续执行
  • breakage:如果在最后一个线程到达之前,正在await的线程因为中断、超时等原因退出了(相当于await提前返回了),那么其他所有正在await的线程都将收到BrokenBarrierException异常。注意barrier action抛异常的话也会造成BrokenBarrierException。如果BrokenBarrierException一旦抛出,除非reset,否则该轮一直处于未完成状态,并且处于breakage状态无法使用。

注意barrier action在最后一个到达的线程await时才执行,执行完毕后所有线程的await才会返回。假如不需要“执行完毕后所有线程的await才会返回”,你只希望在最后一个到达的线程await后所有线程马上得到释放(并且希望action依然由最后一个到达的线程执行),可以这么做:

if (barrier.await() == 0) {
	// action
}

await返回当前线程是第几个到达的线程,0代表最后一个到达(n-1代表第一个, n-2代表第2个...)。

最后,对于breakage发生后的reset,文档建议最好重新new一个CyclicBarrier,而不是reset。首先肯定只能由其中一个线程来reset,既然大家都收到了BrokenBarrierException,谁来reset合适?假如选出了负责reset的那个线程,在其reset之前,其他线程都不能await,这里又存在一个线程间同步问题需要解决。综上,发生breakage后,如果还想继续使用CyclicBarrier,重新new一个是比较好的选择。

代码分析

成员变量

public class CyclicBarrier {
	// Generation代表一轮
  private static class Generation {
    Generation() {}                 
    // 该轮是否broken,即breakage
    boolean broken;
  }
	
  // 保护下面的变量
  private final ReentrantLock lock = new ReentrantLock();
  // 用于唤醒所有等待的线程
  private final Condition trip = lock.newCondition();
  
	// 总线程数
  private final int parties;
	// barrier action
  private final Runnable barrierCommand;
	// 当前轮
  private Generation generation = new Generation();
	// 还没到达的线程数
  private int count;
}

方法

轮次正常或异常结束相关方法:

// 唤醒当前轮所有线程,开启下一轮
private void nextGeneration() {
  trip.signalAll();
  count = parties;
  generation = new Generation();
}
// 将当前轮设置为失败,唤醒当前轮所有线程
private void breakBarrier() {
  generation.broken = true;
  count = parties;
  trip.signalAll();
}

重置:

public void reset() {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    breakBarrier();   // break the current generation
    nextGeneration(); // start a new generation
  } finally {
    lock.unlock();
  }
}

上面的方法比较简单,核心都在doWait方法,这个方法是await方法的实现:

// 以下几种情况会使得dowait返回
// 1. 最后一个线程到达,本轮正常结束,以下其他情况都是本轮broken并抛异常
// 2. 本轮已是broken
// 3. 超时
// 4. 中断
// 5. barrier action抛异常
private int dowait(boolean timed, long nanos)
  throws InterruptedException, BrokenBarrierException,
  TimeoutException {
  final ReentrantLock lock = this.lock;
  // 加锁访问
  lock.lock();
  try {
    final CyclicBarrier.Generation g = generation;
		// 情况2
    if (g.broken)
      throw new BrokenBarrierException();
		// 情况4
    if (Thread.interrupted()) {
      breakBarrier();
      throw new InterruptedException();
    }
		// 还没到达的线程数-1
    int index = --count;
    // 是否最后一个到达
    if (index == 0) {
      Runnable command = barrierCommand;
      // 执行barrier action
      if (command != null) {
        try {
          command.run();
        } catch (Throwable ex) {
					// 情况5
          breakBarrier();
          // 注意,当barrier action抛出异常,本线程抛出的是barrier action的异常
          // 其他线程抛出的是BrokenBarrierException
          throw ex;
        }
      }
      // 执行完毕,本轮成功,开始下一轮
      nextGeneration();
      return 0;
    }

    // 不是最后一个到达的线程
    for (;;) {
      try {
        // 在条件变量上等待
        if (!timed)
          trip.await();
        else if (nanos > 0L)
          nanos = trip.awaitNanos(nanos);
      } catch (InterruptedException ie) {
        // 情况4
        // 如果本轮未结束
        if (g == generation && ! g.broken) {
          breakBarrier();
          throw ie;
        } else {
          // 本轮已经结束(已经下一轮或者已经broken),那么这个中断应该留给下一轮用
          Thread.currentThread().interrupt();
        }
      }

      if (g.broken)
        // 情况2
        throw new BrokenBarrierException();

      if (g != generation)
        // 情况1,本轮成功
        return index;

      if (timed && nanos <= 0L) {
        // 情况3
        breakBarrier();
        throw new TimeoutException();
      }
    }
  } finally {
    // 解锁,退出
    lock.unlock();
  }
}

“不是最后一个到达的线程”的情况下为什么要无限循环执行呢,考虑Condition.await唤醒只有以下几种可能:要么被signal(broken或者新一轮)、要么超时、要么中断,分支基本都覆盖到了,估计是为了兜底,因为说不定Condition.await可能会诈尸,无故唤醒...或是怕别的地方没设计好,可能会来了个不应该来的signal。

参考链接

「StackOverflow」Real Life Examples For CountDownLatch and CyclicBarrier

「Java全栈知识体系」JUC工具类: CyclicBarrier详解

标签:barrier,lock,await,源码,线程,阅读,action,CyclicBarrier
From: https://www.cnblogs.com/nosae/p/17997482

相关文章

  • Java开发的SRM供应商、在线询价、招投标采购一体化系统源码功能解析
    前言:随着全球化和信息化的发展,企业采购管理面临越来越多的挑战。传统的采购方式往往涉及到多个繁琐的步骤,包括供应商筛选、询价、招投标等,这些过程不仅耗时,而且容易出错。为了解决这些问题,供应商、询价、招投标一体化系统应运而生。该系统通过集成供应商管理、询价管理、招投标......
  • zookeeper源码(07)leader、follower和observer
    Leader构造方法publicLeader(QuorumPeerself,LeaderZooKeeperServerzk)throwsIOException{this.self=self;this.proposalStats=newBufferStats();//获取节点间通信地址Set<InetSocketAddress>addresses;if(self.getQuorumListenOnAllI......
  • A025 《极限挑战》编程 源码
    一、课程介绍本节课将利用所学习的知识,制作一个空投物资的动画效果。二、重难点解析whileTruewhileTrue:...当while循环中的代码执行到最后一行后,又会跳转到while循环处开始重新执行下一次循环。获取画笔坐标通过xcor()可以获取到画笔的x坐标值,通过ycor()可以获取......
  • 龙哥量化:通达信(副图)打板专家技术指标源码公式源码选股公式源码
    如果您需要代写公式,请联系我。龙哥QQ:591438821龙哥微信:Long622889新建一个副图公式,放在副图看信号新建一个条件选股公式,用来选股,都用这个源码 X_1:=MA(CLOSE,20);X_2:=HHV(X_1,5);X_3:=X_1-(X_2-X_1);X_4:=MA(CLOSE,60);XG:CLOSE>REF(CLOSE,1)ANDMA(CLOSE,5)>MA(CLOSE,......
  • 《梦断代码》阅读笔记2
    当今社会,软件已经成为人类生活中不可或缺的一部分,“人类文明运行于软件之上”的说法虽然有点自卖自夸,但它很是明确的反应了软件在人类社会中的地位。它存在于厨具里、汽车里、玩具里、建筑中,商业、科研、医疗、基础公共设施哪里都有它的影子,人类生存之所需都系于计算机代码这根易......
  • 龙哥量化:通达信(副图)稳赚趋势策略技术指标源码公式源码
    如果您需要代写公式,请联系我。龙哥QQ:591438821龙哥微信:Long622889介绍:主力线上穿操作线之后,主力线以45°角上穿分水岭,同时操作线也以45°角向上运行,这中情况下涨停概率在90%以上,此时买入 VAR1:=(EMA(C,12)-EMA(C,26))*100+50;VAR2:=EMA(VAR1,9);VAR3:=((HHV(H,21)-C)/(......
  • 《梦断代码》阅读笔记3
    寒假静下来读书的时间比较少,因此我并没有读完《梦断代码》这本有意思的书,以后会慢慢读的,现在说一说目前读完的部分的感受吧。首先,这本书深入讨论了软件开发的复杂性和编程的挑战性,尤其是在项目管理和时间规划方面。对于“软件时间”的分析让我意识到在实际编程中,时间管理并非总是......
  • 龙哥量化:通达信(副图)买在起涨点技术指标源码公式源码
    如果您需要代写公式,请联系我。龙哥QQ:591438821龙哥微信:Long622889黄金点三个时买入,蓝点出时毫不犹豫出半仓,加之和布林轨结合用,站稳中轨持半仓,直到第二波蓝点出就全止盈。出第一蓝点时同时破中轨肯定是大阴线就全止盈出局。VAR1:=1090630;喜来财:DRAWNULL,NODRAW;珍珠点:IF(DA......
  • GNN论文阅读笔记
    DOI10.1109/TNN.2008.2005605任何数据都可以由一张图(Graph)表示,图(Graph)是由一系列的点(vertex)与边(edge)的集合。机器学习的目标是:拟合一个函数τ(G,n) →Rm,即映射图G与其中某一节点n成一个m-dim的实数向量。根据实际任务,这种拟合有所偏向,大体可分为两类:关注于图特征的拟合......
  • 2024.1.18《程序员的修炼之道:从小工到专家》阅读笔记1
    《程序员的修炼之道:从小工到专家》是一本经典的计算机编程领域的书籍,由AndrewHunt和DavidThomas合著。这本书以富有启发性的方式,向读者展示了成为一名优秀程序员的道路。本书以通俗易懂的语言,深入浅出地解释了编程领域的一些基本概念和原则。作者通过生动的案例和具体的实践经验......