首页 > 编程语言 >CountDownLatch源码解析

CountDownLatch源码解析

时间:2023-02-17 07:11:22浏览次数:37  
标签:int countDown 计数 源码 线程 doneSignal CountDownLatch 解析

CountDownLatch源码解析

描述:

一种同步辅助工具,允许一个或多个线程等待在其他线程中执行的一组操作完成。

用给定的count初始化 CountDownLatch 。因为调用了 countDown() 方法, await() 方法会一直阻塞,直到当前计数为零。在这之后,所有等待的线程都会被释放,后续对 await() 的调用都会立即返回。这是一种一次性现象 —— 计数无法重置。如果你需要一个重置计数的版本,可以考虑使用 CyclicBarrier 。

CountDownLatch 是一个通用的同步工具,可以用于许多目的。一个初始化为1的 CountDownLatch 相当于一个简单的 on/off闩锁,即门:调用 await() 的所有线程都会在门上等待,直到调用 countDown() 的线程打开门。初始化为N的 CountDownLatch 可以用来使一个线程等待,直到 N 线程完成某个操作,或者某个操作完成N次。

CountDownLatch 有一个很有用的属性,它不要求调用 countDown 的线程等待计数为0后再继续,它不要求调用 countDown 的线程在继续之前等待计数为零,它只是阻止任何线程继续执行 await(),直到所有线程都可以通过。

示例用法:

下面是两个类,其中一组工作线程使用两个倒计时锁存器:

第一个是一个启动信号,阻止任何 worker 继续前进,直到 Driver 准备好让他们继续前进;

第二个是一个完成信号,它允许 Driver 等待直到所有工人都完成工作。

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);
    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();
    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

 class Worker implements Runnable {
   private final CountDownLatch startSignal;
   private final CountDownLatch doneSignal;
   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
   }
   public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
   }
   void doWork() { ... }
 }

另一种典型的用法是将一个问题划分为 N 个部分,用一个 Runnable 对象来描述每个部分,该Runnable对象执行该部分,并在闩锁时计数,然后将所有的Runnable对象排队到一个执行器。当所有子部分完成时,协调线程将能够通过await。(当线程必须以这种方式重复计数时,请使用 CyclicBarrier。)

class Driver2 { // ...
  void main() throws InterruptedException {
    CountDownLatch doneSignal = new CountDownLatch(N);
    Executor e = ...
    for (int i = 0; i < N; ++i) // create and start threads
      e.execute(new WorkerRunnable(doneSignal, i));
    doneSignal.await();           // wait for all to finish
  }
}

class WorkerRunnable implements Runnable {
  private final CountDownLatch doneSignal;
  private final int i;
  WorkerRunnable(CountDownLatch doneSignal, int i) {
    this.doneSignal = doneSignal;
    this.i = i;
  }
  public void run() {
    try {
      doWork(i);
      doneSignal.countDown();
    } catch (InterruptedException ex) {} // return;
  }
  void doWork() { ... }
}

内存一致性影响:直到计数为零,在调用线程之前执行操作

源码:

public class CountDownLatch {
    
    /**
     * CountDownLatch的同步控制。使用AQS状态表示计数
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    /**
     * 指定计数器的构造函数
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * 导致当前线程等待,直到闩锁计数到0,除非线程被中断。
     *
     * 1.如果当前计数为零,则此方法立即返回。
     * 2.如果当前计数大于0,那么当前线程将被禁用,用于线程调度,并处于休眠状态,直到发生以下两种情况之一: 由于调用了 countDown 方法,计数器变为0 或 其他线程中断当前线程。
     * 3.如果当前线程: 在进入此方法时设置其中断状态;或 当等待时,线程中断 , 然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * 除非线程是被中断,或者指定的等待时间已经过,否则当前线程将等待到锁存倒计时为零。
     *
     * 1.如果当前计数为零,则该方法立即返回值 true。
     * 2.如果当前线程计数大于0,那么当前线程出于线程调度的目的被禁用,并处于休眠状态,直到发生以下三种情况之一: 由于调用了 countDown 方法,计数器变为0 或 其他线程中断当前线程。
     * 3.如果当前线程: 在进入该方法时设置了它的中断状态;或 当等待时,线程中断 。然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     * 4.如果指定的等待时间过去了,则返回值  false 。如果时间小于或等于零,该方法将根本不等待。
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * 减少锁存器的计数,如果计数为零,则释放所有等待线程。
     *
     * 1.如果当前计数值大于零,则减1。如果新的计数为0,则重新启用所有等待的线程,用于线程调度。
     * 2.如果当前计数等于零,那么什么也不会发生。
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * 返回当前计数。
     */
    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

整体流程:

  1. 通过构造函数初始化 AQS 内部类的状态值 state

  2. 主线程调用 await 方法,当 state = 0 时,继续向下执行,否则进入阻塞队列,等待唤醒

  3. 子线程调用 countDown 方法,使 state-1 ,当减完之后的 state != 0 , 不做任何操作,否则是否所有等待的线程(释放主线程的阻塞)

设计思路:

主要根据 state 的值是否大于 0 ,

大于0主线程是否阻塞,当子线程逐步做state的减一操作,state = 0 时,释放主线程,主线程继续执行。以达到计数器到零之后再继续操作

小于等于0,主线程不阻塞,继续执行

标签:int,countDown,计数,源码,线程,doneSignal,CountDownLatch,解析
From: https://www.cnblogs.com/coolyang/p/17126155.html

相关文章

  • Llvm 源码结构及测试基础
    Llvm源码结构及测试基础Llvm源码目录功能简介了解一下Llvm源码工程目录介绍、Llvm相关lib库的介绍、Llvm生成的工具链介绍,方便我们后期学习Llvm。LLVM源码工程目录介......
  • 深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析
    本文首发我的微信公众号:徐公,想成为一名优秀的Android开发者,需要一份完备的知识体系,在这里,让我们一起成长,变得更好~。前言前一阵子,写了几篇Android启动优化的文章......
  • 13 drf-解析器
    之前使用request.data获取请求体中的数据。这个reqeust.data的数据怎么来的呢?其实在drf内部是由解析器,根据请求者传入的数据格式+请求头来进行处理。 解析器可......
  • SpringMVC源码(八):Controller控制器执行流程
    在MVC请求流程中,获取到HandlerAdapter适配器后,会执行handler处理器(Controller控制器)的相关逻辑,通过适配器的handle()方法,完成目标Controller处理器的调用。在源码(七......
  • dom4j解析和生成xml文件
    解析xml大致步骤:1:创建SAXReader;2:使用SAXReader解析指定的xml文档信息,并返回对应Document对象。Document对象中就包含了该xml文中的所有信息以及结构了。3:根据文档......
  • Epoll原理解析--网卡接收数据说起
     转至 https://blog.csdn.net/armlinuxww/article/details/92803381 太重要了怕丢失,冒昧转一下  从网卡接收数据说起下边是一个典型的计算机结构图,计算机由CPU......
  • 解析MYSQL建表语句,生成表结构的JSON
    根据建表语句解析表结构,并将表结构解析为JSON。根据MYSQL的建表语句,建表语句:CREATETABLE`TEST`(`ID`varchar(56)NOTNULL,`CREAETE_TIME`datetimeN......
  • 数据解析-正则匹配
    一、正则基础1、为什么使用正则需求判断一个字符串是否是手机号解决编写一个函数,给函数一个字符串,如果是手机号则返回True,否则返回False代码defisPhone(phon......
  • 【android】音视频开发五:学习MediaExtractor 和 MediaMuxer,知道如何解析和封装 mp4 文
    MediaExtractorMediaExtractor顾名思义就是多媒体提取器,主要负责:获取媒体文件的格式,包括音视频轨道,编码格式,宽高,采样率,声道数等,分离音频流,视频流,读取分离后的音视频数据......
  • PHP 之自定义模板解析
    一、解析列表1、效果图 2、示例代码<?phpinclude_once'parse.func.php';$data=array(array('scode'=>2,'link'=>'https://www.b......