zoukankan      html  css  js  c++  java
  • 【JUC源码解析】CountDownLatch

    简介

    CountDownLatch,是一个同步器,允许一个或多个线程等待,直到一组操作在其他线程中完成。

    概述

    初始CountDownLatch时,会给定count,await方法会阻塞,直到count减小到0,countDown方法会是count减1,count不能被重置。

    应用

    例一

    描述

    有1个老板,雇了10工人,工人就位后,并不是立即工作,而是等到老板发出指令,才会开始工作,每个工人完成工作后,也会发出一个指令反馈完成此工作,而老板会等待所有的工人都完成工作,然后做下一步打算。

    代码

     1 public class Driver {
     2     private static final int N = 10;
     3 
     4     public static void main(String[] args) throws InterruptedException {
     5         CountDownLatch startSignal = new CountDownLatch(1);
     6         CountDownLatch doneSignal = new CountDownLatch(N);
     7 
     8         for (int i = 0; i < N; ++i)
     9             new Thread(new Worker("[工人" + i + "]", startSignal, doneSignal)).start(); // 启动工作线程
    10 
    11         System.out.println("[老板]发出开始信号");
    12         startSignal.countDown(); // 发出开始信号
    13         doneSignal.await(); // 等待工人们完成
    14         System.out.println("[老板]收到所有工人完成的信号");
    15     }
    16 }
    17 
    18 class Worker implements Runnable {
    19     private final String name;
    20     private final CountDownLatch startSignal;
    21     private final CountDownLatch doneSignal;
    22 
    23     Worker(String name, CountDownLatch startSignal, CountDownLatch doneSignal) {
    24         this.name = name;
    25         this.startSignal = startSignal;
    26         this.doneSignal = doneSignal;
    27     }
    28 
    29     public void run() {
    30         try {
    31             startSignal.await(); // 工人们在此等待老板的开工信号
    32             System.out.println(this.name + " >> 开始工作");
    33             doWork(); // 开始做工作
    34             System.out.println(this.name + " << 完成工作");
    35             doneSignal.countDown(); // 发出完成信号
    36         } catch (InterruptedException ex) {
    37         }
    38     }
    39 
    40     void doWork() {
    41         System.out.println(this.name + " == 正在工作");
    42     }
    43 }

    输出

    [老板]发出开始信号
    [工人0] >> 开始工作
    [工人0] == 正在工作
    [工人0] << 完成工作
    [工人2] >> 开始工作
    [工人2] == 正在工作
    [工人2] << 完成工作
    [工人1] >> 开始工作
    [工人1] == 正在工作
    [工人1] << 完成工作
    [工人4] >> 开始工作
    [工人3] >> 开始工作
    [工人3] == 正在工作
    [工人3] << 完成工作
    [工人4] == 正在工作
    [工人4] << 完成工作
    [工人7] >> 开始工作
    [工人6] >> 开始工作
    [工人5] >> 开始工作
    [工人5] == 正在工作
    [工人5] << 完成工作
    [工人6] == 正在工作
    [工人7] == 正在工作
    [工人6] << 完成工作
    [工人7] << 完成工作
    [工人8] >> 开始工作
    [工人8] == 正在工作
    [工人8] << 完成工作
    [工人9] >> 开始工作
    [工人9] == 正在工作
    [工人9] << 完成工作
    [老板]收到所有工人完成的信号

    例二

    描述

    有1个很大的任务,可以分成10个子任务,交给10个线程去工作,并在最后汇总结果。

    代码

     1 public class Driver2 {
     2     private static final int N = 10;
     3 
     4     public static void main(String[] args) throws InterruptedException {
     5         CountDownLatch startSignal = new CountDownLatch(1);
     6         CountDownLatch doneSignal = new CountDownLatch(N);
     7         ExecutorService e = Executors.newFixedThreadPool(N);
     8 
     9         for (int i = 0; i < N; ++i)
    10             e.execute(new WorkerRunnable(startSignal, doneSignal, i));
    11 
    12         e.shutdown();
    13         System.out.println("[总任务]分成" + N + "子任务并开始执行");
    14         startSignal.countDown(); // 发出开始信号
    15         doneSignal.await();
    16         System.out.println("[总任务]已经完成");
    17     }
    18 }
    19 
    20 class WorkerRunnable implements Runnable {
    21     private final CountDownLatch startSignal;
    22     private final CountDownLatch doneSignal;
    23     private final int i;
    24 
    25     WorkerRunnable(CountDownLatch startSignal, CountDownLatch doneSignal, int i) {
    26         this.startSignal = startSignal;
    27         this.doneSignal = doneSignal;
    28         this.i = i;
    29     }
    30 
    31     public void run() {
    32         try {
    33             startSignal.await();
    34         } catch (InterruptedException e) {
    35         }
    36         System.out.println("[子任务" + i + "]>>开始执行");
    37         doWork(i);
    38         doneSignal.countDown();
    39         System.out.println("[子任务" + i + "]==已经完成");
    40     }
    41 
    42     void doWork(int i) {
    43         System.out.println("[子任务" + i + "]==正在执行");
    44     }
    45 }

    输出

    [总任务]分成10子任务并开始执行
    [子任务1]>>开始执行
    [子任务1]==正在执行
    [子任务1]==已经完成
    [子任务4]>>开始执行
    [子任务4]==正在执行
    [子任务4]==已经完成
    [子任务3]>>开始执行
    [子任务3]==正在执行
    [子任务3]==已经完成
    [子任务0]>>开始执行
    [子任务0]==正在执行
    [子任务0]==已经完成
    [子任务6]>>开始执行
    [子任务6]==正在执行
    [子任务6]==已经完成
    [子任务2]>>开始执行
    [子任务2]==正在执行
    [子任务2]==已经完成
    [子任务5]>>开始执行
    [子任务5]==正在执行
    [子任务5]==已经完成
    [子任务7]>>开始执行
    [子任务8]>>开始执行
    [子任务9]>>开始执行
    [子任务7]==正在执行
    [子任务9]==正在执行
    [子任务8]==正在执行
    [子任务9]==已经完成
    [子任务7]==已经完成
    [子任务8]==已经完成
    [总任务]已经完成

    源码分析

     1 public class CountDownLatch {
     2     private static final class Sync extends AbstractQueuedSynchronizer { // 内部类,继承自AQS
     3         private static final long serialVersionUID = 4982264981922014374L;
     4 
     5         Sync(int count) { // 构造方法
     6             setState(count); // 设置state
     7         }
     8 
     9         int getCount() {
    10             return getState(); // 获取state
    11         }
    12 
    13         protected int tryAcquireShared(int acquires) { // 获取共享锁
    14             return (getState() == 0) ? 1 : -1; // state为0,成功获得锁,否则失败,去等待
    15         }
    16 
    17         protected boolean tryReleaseShared(int releases) { // 释放共享锁
    18             for (;;) {
    19                 int c = getState(); // 获得state
    20                 if (c == 0) // state为0,已经没有线程在等待,不用唤醒
    21                     return false;
    22                 int nextc = c - 1; // state减1
    23                 if (compareAndSetState(c, nextc)) // 设置state
    24                     return nextc == 0; // 减到0时,唤醒后面的线程
    25             }
    26         }
    27     }
    28 
    29     private final Sync sync;
    30 
    31     public CountDownLatch(int count) { // 构造方法,给定count
    32         if (count < 0)
    33             throw new IllegalArgumentException("count < 0");
    34         this.sync = new Sync(count); // count赋值给state,指定需要释放count次锁,才会唤醒所有阻塞在该锁上的线程
    35     }
    36 
    37     public void await() throws InterruptedException {
    38         sync.acquireSharedInterruptibly(1); // 调用同步器的响应中断的共享锁
    39     }
    40 
    41     public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
    42         return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); // 调用同步器的支持超时的共享锁
    43     }
    44 
    45     public void countDown() {
    46         sync.releaseShared(1); // 每调用一次countDown方法,就释放一次锁
    47     }
    48 
    49     public long getCount() {
    50         return sync.getCount(); // 获取当前state
    51     }
    52 
    53     public String toString() {
    54         return super.toString() + "[Count = " + sync.getCount() + "]";
    55     }
    56 }

    CountDownLatch,内部维护一个Sync类,该类继承自AbstractQueuedSynchronizer,所有的逻辑都在Sync类中完成。

    当一组线程调用await方法时,其实调用的是Sync的acquireSharedXXX方法,该方法首先判断tryAcquireShared方法的返回值是否大于0(state为0时返回1,否则,返回-1),也就是说,该共享锁是否还有空位,state初始时便会有count设置为大于0的值,所以,一有线程调用await方法(await -> acquireSharedXXX -> tryAcquireShared),其实,进入的是Sync的acquireSharedXXX方法调用的doAcquireSharedXXX方法(该方法在AQS同步器里),进而入队了(寻找安全停靠点停下,或继续抢占共享锁),也就是说,等待了。

    再看countDown方法,该方法调用的是Sync的releaseShared方法,该方法会调用tryReleaseShared方法,根据此方法的返回结果,决定是否唤醒阻塞在该共享锁上的线程。查看tryReleaseShared方法的逻辑可知,该方法只在state减到0时,才返回true。当然,如果state已经为0,有线程再调用此方法时,依然返回false,因为,此刻,阻塞在该共享锁上的线程已经被释放过了。

    行文至此结束。


    尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_cdl.html

  • 相关阅读:
    Ensemble.Tofino运行报错Unexpected java bridge exception的解决
    【Flex Viewer】源码介绍(3)Flex Viewer架构解析
    【Flex Viewer】源码介绍(1)Flex Viewer简介
    【Flex Viewer】 开发教程(4)Widget与WidgetTemplate
    【Flex Viewer】源码介绍(2)Flex Viewer源码包结构
    Flex与.NET互操作:基于WebService的数据访问
    浅谈我对几个Web前端开发框架的比较【转帖】
    11个GIS相关的iphone应用程序(Apps)
    【Flex Viewer】 开发教程(1)Flex Viewer配置文件
    VS2010 设置include路径
  • 原文地址:https://www.cnblogs.com/aniao/p/aniao_cdl.html
Copyright © 2011-2022 走看看