【JUC源码解析】CountDownLatch
简介
CountDownLatch,是一个同步器,允许一个或多个线程等待,直到一组操作在其他线程中完成。
概述
初始CountDownLatch时,会给定count,await方法会阻塞,直到count减小到0,countDown方法会是count减1,count不能被重置。
应用
例一
描述
有1个老板,雇了10工人,工人就位后,并不是立即工作,而是等到老板发出指令,才会开始工作,每个工人完成工作后,也会发出一个指令反馈完成此工作,而老板会等待所有的工人都完成工作,然后做下一步打算。
代码
public class Driver {
private static final int N = 10;
public static void main(String[] args) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i)
new Thread(new Worker("[工人" + i + "]", startSignal, doneSignal)).start(); // 启动工作线程
System.out.println("[老板]发出开始信号");
startSignal.countDown(); // 发出开始信号
doneSignal.await(); // 等待工人们完成
System.out.println("[老板]收到所有工人完成的信号");
}
}
class Worker implements Runnable {
private final String name;
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(String name, CountDownLatch startSignal, CountDownLatch doneSignal) {
this.name = name;
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await(); // 工人们在此等待老板的开工信号
System.out.println(this.name + " >> 开始工作");
doWork(); // 开始做工作
System.out.println(this.name + " << 完成工作");
doneSignal.countDown(); // 发出完成信号
} catch (InterruptedException ex) {
}
}
void doWork() {
System.out.println(this.name + " == 正在工作");
}
}
输出
[老板]发出开始信号
[工人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个线程去工作,并在最后汇总结果。
代码
public class Driver2 {
private static final int N = 10;
public static void main(String[] args) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
ExecutorService e = Executors.newFixedThreadPool(N);
for (int i = 0; i < N; ++i)
e.execute(new WorkerRunnable(startSignal, doneSignal, i));
e.shutdown();
System.out.println("[总任务]分成" + N + "子任务并开始执行");
startSignal.countDown(); // 发出开始信号
doneSignal.await();
System.out.println("[总任务]已经完成");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch startSignal, CountDownLatch doneSignal, int i) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
try {
startSignal.await();
} catch (InterruptedException e) {
}
System.out.println("[子任务" + i + "]>>开始执行");
doWork(i);
doneSignal.countDown();
System.out.println("[子任务" + i + "]==已经完成");
}
void doWork(int i) {
System.out.println("[子任务" + i + "]==正在执行");
}
}
输出
[总任务]分成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]==已经完成
[总任务]已经完成
源码分析
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer { // 内部类,继承自AQS
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) { // 构造方法
setState(count); // 设置state
}
int getCount() {
return getState(); // 获取state
}
protected int tryAcquireShared(int acquires) { // 获取共享锁
return (getState() == 0) ? 1 : -1; // state为0,成功获得锁,否则失败,去等待
}
protected boolean tryReleaseShared(int releases) { // 释放共享锁
for (;;) {
int c = getState(); // 获得state
if (c == 0) // state为0,已经没有线程在等待,不用唤醒
return false;
int nextc = c - 1; // state减1
if (compareAndSetState(c, nextc)) // 设置state
return nextc == 0; // 减到0时,唤醒后面的线程
}
}
}
private final Sync sync;
public CountDownLatch(int count) { // 构造方法,给定count
if (count < 0)
throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count); // count赋值给state,指定需要释放count次锁,才会唤醒所有阻塞在该锁上的线程
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 调用同步器的响应中断的共享锁
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); // 调用同步器的支持超时的共享锁
}
public void countDown() {
sync.releaseShared(1); // 每调用一次countDown方法,就释放一次锁
}
public long getCount() {
return sync.getCount(); // 获取当前state
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
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
【JUC源码解析】CountDownLatch的更多相关文章
- 【JUC源码解析】ScheduledThreadPoolExecutor
简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...
- 【JUC源码解析】Exchanger
简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...
- 【JUC源码解析】SynchronousQueue
简介 SynchronousQueue是一种特殊的阻塞队列,该队列没有容量. [存数据线程]到达队列后,若发现没有[取数据线程]在此等待,则[存数据线程]便入队等待,直到有[取数据线程]来取数据,并释 ...
- 【JUC源码解析】ForkJoinPool
简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...
- 【JUC源码解析】DelayQueue
简介 基于优先级队列,以过期时间作为排序的基准,剩余时间最少的元素排在队首.只有过期的元素才能出队,在此之前,线程等待. 源码解析 属性 private final transient Reentra ...
- 【JUC源码解析】CyclicBarrier
简介 CyclicBarrier,一个同步器,允许多个线程相互等待,直到达到一个公共屏障点. 概述 CyclicBarrier支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后 ...
- 【JUC源码解析】ConcurrentLinkedQueue
简介 ConcurrentLinkedQueue是一个基于链表结点的无界线程安全队列. 概述 队列顺序,为FIFO(first-in-first-out):队首元素,是当前排队时间最长的:队尾元素,当 ...
- Jdk1.6 JUC源码解析(12)-ArrayBlockingQueue
功能简介: ArrayBlockingQueue是一种基于数组实现的有界的阻塞队列.队列中的元素遵循先入先出(FIFO)的规则.新元素插入到队列的尾部,从队列头部取出元素. 和普通队列有所不同,该队列 ...
- Jdk1.6 JUC源码解析(6)-locks-AbstractQueuedSynchronizer
功能简介: AbstractQueuedSynchronizer(以下简称AQS)是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore.CountDow ...
随机推荐
- servlet的生命周期详解
一.servlet生命周期原理解析 1.Servlet生命周期分为三个阶段: (1)初始化阶段 调用init()方法 (2)响应客户请求阶段 调用service()方法 (3)终止阶段 调用dest ...
- POJ2074 Line of Sight
嘟嘟嘟 题意:用一条水平线段表示以栋房子:\((x_0, y_0)(x_0', y_0)\).然后有一条低于房子的水平线段\(l_0\),代表你可以到的位置.接下来输入一个数\(n\),一下\(n\) ...
- PHP异步:在PHP中使用 fsockopen curl 实现类似异步处理的功能
PHP从主流来看,是一门面向过程的语言,它的最大缺点就是无法实现多线程管理,其程序的执行都是从头到尾,按照逻辑一路执行下来,不可能出现分支,这一点是限制php在主流程序语言中往更高级的语言发展的原因之 ...
- js apply的用法
问题: 1.apply和call的区别在哪里 2.什么情况下用apply,什么情况下用call 3.apply的其他巧妙用法(一般在什么情况下可以使用apply) 我首先从网上查到关于apply和ca ...
- Java 获取指定包下的所有类
package com.s.rest.util; import java.io.File; import java.io.FileFilter; import java.io.IOException; ...
- C# 中的#if、#elif、#else、#endif等条件编译符号 (转载)
这些是C#中的条件编译符号.这些指令我在项目中遇到过,查过网络,问过人(当然,既不认识大牛,也不认识小牛,所以没什么收获).今天翻看一本资料,有提到这个方面的东西,所以写下来和能看到这篇文章的人一起学 ...
- BroadcastReceiver(接收广播)
Broadcast Receiver用于接收并处理广播通知(broadcast announcements).多数的广播是系统发起的,如地域变换.电量不足.来电来信等.程序也能够播放一个广播. 程序能 ...
- Notes 20180312 : String第四讲_String上的操作
作为一个基本的工具类,同时又是使用频率很高的类,Java为其提供了丰富的方法支持.Java中的String类中包含了50多个方法.最令人惊讶的是绝大多数方法都很有用,下面我们根据功能来分类介绍一下: ...
- everything对已经不存在的文件还进行索引,报错“系统找不到指定的驱动器”
使用everything时,总是报下面的错.也就是对于已经不存在的文件或者文件夹还保留着对应的索引. 如下图: 这个其实我们在选项中设置一下规则就可以了. 一. 二. 三. ok啦.
- Ubuntu install 错误 E:Unable to locate package
今天在 Ubuntu 上执行 sudo apt install sl 命令,结果报错:E:Unable to locate package sl 上网查询了一下,先更新一下 apt-get,执行:su ...