并发编程之:CountDownLatch
大家好,我是小黑,一个在互联网苟且偷生的农民工。
先问大家一个问题,在主线程中创建多个线程,在这多个线程被启动之后,主线程需要等子线程执行完之后才能接着执行自己的代码,应该怎么实现呢?
Thread.join()
看过我 并发编程之:线程 的朋友应该知道怎么做,在Thread类中有一个方法join(),这个方法是一个阻塞方法,当前线程会等待调动join()方法的线程死亡之后再继续执行。
我们通过代码来看看执行结果。
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run ~");
});
t.start();
t.join();
}
System.out.println("main线程执行结束");
}
}
从结果可以看出,main线程要等到所有子线程都执行完之后才会继续执行,并且每一个子线程是按顺序执行的。
我们在来看一下join()方法是如何让主线程阻塞的呢?来看一下源码。
public final void join() throws InterruptedException {
// 默认传入0毫秒
join(0);
}
// 本方法是synchronized的
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 测试当前线程是否还活着
while (isAlive()) {
// 执行wait,当前线程等待
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从join方法的源码中我们可以看到几个重要的信息,首先join()方法默认是等待0毫秒;join(long millis)方法是一个synchronized方法;循环判断当前线程是否还活着。什么意思呢?
- main线程在调用线程T的join()方法时,会先获取T对象的锁;
- 在join方法中会调用T对象的wait()方法等待,而wait()方法会释放T对象的锁,并且main线程在执行完wait()之后会进入阻塞状态;
- 最后main线程在被notify唤醒之后,需要再循环判断T对象是否还活着,如果还活着会再次执行wait()。
而在线程执行完run()方法之后,JVM会调用该线程的exit()方法,通过notifyAll()唤醒处于等待状态的线程。
private void exit() {
if (group != null) {
// 终止group中的线程this
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
// 唤醒等待线程
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
细心的话你会发现,使用Thread.join()只能做到让一个线程执行完之后,做不到同时等待多个线程,比如我们上面的代码,线程1执行完之后才能执行线程2,无法做到让线程1和线程2同时处理。
CountDownLatch
而在JUC包中的工具类CountDownLatch具备和Thread.join()方法同样的能力,可以等待一个线程执行完之后再处理,并且支持同时等待多个线程。我们来修改一下上面Thread.join()的例子。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run ~");
countDownLatch.countDown();
});
t.start();
}
countDownLatch.await();
System.out.println("main线程执行结束");
}
}
CountDownLatch需要在创建时指定一个计数值,在子线程中执行完之后调用countDown()方法进行递减,主线程的await()方法会等到值减为0之后继续执行。
从运行结果我们可以看到,100个子线程并不是按顺序执行的,而是随机的。
我们通过CountDownLatch的源码来看一下是如何实现的。
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
在CountDownLatch中我们看到有一个Sync变量,从上一期AQS源码解析内容中我们知道Sync是AQS的一个子类实现;
首先构造方法传入的count值会作为参数赋值给Sync中的state变量。
然后我们来看一下在线程中的CountDownLath.countDown()方法会做些什么事情。
public void countDown() {
// 释放共享锁
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
如果有看我上期AQS源码解析的同学一定很熟悉,这段代码就是共享锁的解锁过程,本质上就是对state-1。
那么主线程是如何实现的等待呢?我们猜一下,应该是去判断state有没有减为0,如果减为0则代表所有的线程都执行完countDown()方法,则可以继续执行,如果state还不等于0,则表示还有线程正在执行,等待就OK啦。
我们来看看源码,是否和我们猜想的一样呢?
public void await() throws InterruptedException {
// 可中断地获取共享锁
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取共享锁
if (tryAcquireShared(arg) < 0)
// state还不是1
doAcquireSharedInterruptibly(arg);
}
// 获取锁状态,当state减为0时,返回1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 排入队尾
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 线程在这里park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以发现await()方法和我们昨天看到的共享锁解锁过程一模一样,符合我们的猜想。
所以,CountDownLatch的底层实现也是依靠AQS来完成的,现在大家肯定对于AQS有更深刻的认识了。
区别
我们现在来对比一下Thread.join()和CountDownLatch有哪些区别:
- Thread.join()是Thread类的一个方法,而CountDownLatch是JUC包中的一个工具类;
- Thread.join()的实现是依靠Object的wait()和notifyAll()来完成的,而CountDownLatch是通过AQS完成的;
- Thread.join()只支持让一个线程等待,不支持同时等待多个线程,而CountDownLatch可以支持,所以CountDownLatch的效率要更高。
好的,本期内容就到这里,我们下期见。
并发编程之:CountDownLatch的更多相关文章
- Java并发编程之CountDownLatch,CyclicBarrier实现一组线程相互等待、唤醒
java多线程应用场景不少,有时自己编写代码又不太容易实现,好在concurrent包提供了不少实现类,还有google的guava包更是提供了一些最佳实践,这让我们在面对一些多线程的场景时,有了不少 ...
- Java并发编程之CountDownLatch
一.场景描述 在多线程程序设计中,经常会遇到一个线程等待一个或多个线程的场景 例如:百米赛跑,十名运动员同时起跑,由于速度的快慢,肯定有先到达和后到达的,而终点有个统计成绩的仪器,当所有选手到达终点时 ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
- 并发编程之CountDownLatch
在前面的两篇文章中我们分别用volatile.notify()和wait()分别实现了一个场景,我们再来回顾一下前面的场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一 ...
- Java并发编程之CountDownLatch的用法
一.含义 CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能.CountDownLatch是一个同步的辅助类,它可以允许一个或多个线程等待, ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之J.U.C的第二篇
并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...
- [转载]并发编程之Operation Queue和GCD
并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
随机推荐
- xmind2020 zen 10.2.1win/mac/linux安装教程
xmind是一款优秀的思维导图软件,本文教大家如何安装xmind zen 2020 10.2.1版本,解锁使用全部功能,包括去掉xmind zen水印.上传图片等功能,支持windows/mac/li ...
- 构建后端第6篇之---java 多态的本质 父类引用 指向子类实现
张艳涛写于2021-2-20 今天来个破例了,不用英文写了,今天在家里电脑写的工具不行,简单的说 主题是:java多态的原理与实现 结论是:java的多态 Father father= new Son ...
- AWS 安全信息泄露-----21天烧了27万
安全问题一直都是个老生常谈的话题,对于我们做IT的来说,是更为重视的.从使用开发工具的是否授权合规,到从事的工作内容是否合法.我们都应该认真的思考一下这些问题,毕竟我们要靠IT这门手艺吃饭. 2021 ...
- 【Python机器学习实战】感知机和支持向量机学习笔记(三)之SVM的实现
前面已经对感知机和SVM进行了简要的概述,本节是SVM算法的实现过程用于辅助理解SVM算法的具体内容,然后借助sklearn对SVM工具包进行实现. SVM算法的核心是SMO算法的实现,首先对SMO算 ...
- jmeter正则表达式介绍
分三个层次介绍: 1. jmeter正则表达式有什么作用? 2. 正则表达式在哪? 3. 正则表达式怎么用? 1. jmeter正则表达式有什么作用? 答:提取请求中返回的数据, 然后获取的数据放入其 ...
- 花1个月时间准备 面试华为,薪资和定级都谈好了却被拒,HR竟说......
说在前面,千万不要频繁跳槽. 本来华为很想去的,面试前花了一个月的时间准备,面试过程挺顺利的,也拒绝了其他的所有面试邀请,而我拒绝其他面试邀请的底气,则是之前面试过程中的良好表现,薪资和定级都谈好了. ...
- [TensorFlow2.0]-正则化
本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...
- Markdown插入LaTex数学公式
本文转载自Nautilus_sailing的试试LaTeX插入数学公式,内容有所改动 今天写了一篇随笔,其中需要写几个数学式子,但是我又不想直接将公式做成图片后插入,我觉得很不美观还麻烦.但是我也不会 ...
- 尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性
本文首发于<尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性> 概述 .NET开发者们大家好,我是Rector. 几天前(美国时间2 ...
- MySQL-18-MHA+Atlas读写分离架构
Atlas介绍 Atlas是由Qihoo 360 Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目 它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了 ...