CountDownLatch能不能在多个线程上添加await?
在CountDownLatch
类的使用过程中,发现了一个很奇怪的现象:
CountDownLatch countDownLatch = new CountDownLatch(2);
Runnable taskMain = () -> {
try {
countDownLatch.await(); // 等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("继续执行任务");
};
Runnable taskMain1 = () -> {
try {
countDownLatch.await(); // 等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("继续执行任务2");
};
Runnable task1 = () -> {
countDownLatch.countDown(); // 计数器 -1
System.out.println("前置任务1完成");
};
Runnable task2 = () -> {
countDownLatch.countDown(); // 计数器 -1
System.out.println("前置任务2完成");
};
new Thread(taskMain).start();
new Thread(taskMain1).start();
new Thread(task1).start();
new Thread(task2).start();
在这个地方使用了两个await
,希望在两个前置线程执行完成之后再执行剩下的两个线程。但是结果有点特别:
继续执行任务
前置任务1完成
前置任务2完成
继续执行任务2
我发现taskMain
的任务首先被执行了。
按照逻辑来说,在第一个await
执行中,由于此时AQS
的state
值等于计数器设置的count
值2,state
必须等于0时他才能拿到锁,所以此时他被挂起。第二个也是如此,那么为什么会出现第一个await
被执行了呢?
我从头捋一遍代码,当第一个await
被调用后:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted() ||
(tryAcquireShared(arg) < 0 &&
acquire(null, arg, true, true, false, 0L) < 0))
throw new InterruptedException();
}
// 这是在CountDownLatch中复写的方法,忘了方便观看放到一起了。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
因为此时state
值为2,所以tryAcquireShared(arg) < 0
成立,此时会调用acquire
尝试去获取锁。
/**
* Main acquire method, invoked by all exported acquire methods.
*
* @param node null unless a reacquiring Condition
* @param arg the acquire argument
* @param shared true if shared mode else exclusive
* @param interruptible if abort and return negative on interrupt
* @param timed if true use timed waits
* @param time if timed, the System.nanoTime value to timeout
* @return positive if acquired, 0 if timed out, negative if interrupted
*/
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0; // retries upon unpark of first thread
boolean interrupted = false, first = false;
Node pred = null; // predecessor of node when enqueued
/*
* Repeatedly:
* Check if node now first
* if so, ensure head stable, else ensure valid predecessor
* if node is first or not yet enqueued, try acquiring
* else if node not yet created, create it
* else if not yet enqueued, try once to enqueue
* else if woken from park, retry (up to postSpins times)
* else if WAITING status not set, set and retry
* else park and clear WAITING status, and check cancellation
*/
for (;;) {
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
if (pred.status < 0) {
cleanQueue(); // predecessor cancelled
continue;
} else if (pred.prev == null) {
Thread.onSpinWait(); // ensure serialization
continue;
}
}
if (first || pred == null) {
boolean acquired;
try {
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
}
if (node == null) { // allocate; retry before enqueue
if (shared)
node = new SharedNode();
else
node = new ExclusiveNode();
} else if (pred == null) { // try to enqueue
node.waiter = current;
Node t = tail;
node.setPrevRelaxed(t); // avoid unnecessary fence
if (t == null)
tryInitializeHead();
else if (!casTail(t, node))
node.setPrevRelaxed(null); // back out
else
t.next = node;
} else if (first && spins != 0) {
--spins; // reduce unfairness on rewaits
Thread.onSpinWait();
} else if (node.status == 0) {
node.status = WAITING; // enable signal and recheck
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)
LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible)
break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}
由此可以看出第一次运行之后,创建节点加入到CLH队列中,然后被LockSupport.park(this)
挂起。这部分跟我预想的一样。第二次也是这样,按理说没什么区别啊。
我不信邪,又重新试了一遍。这次我添加了计数器的个数(15),并且每次都输出当前的state
值。
前置任务b完成:13
前置任务a完成:13
前置任务a完成:12
前置任务b完成:11
前置任务a完成:10
前置任务b完成:9
前置任务a完成:8
前置任务b完成:7
前置任务a完成:6
前置任务b完成:5
前置任务a完成:4
前置任务b完成:3
前置任务a完成:2
前置任务b完成:1
继续执行任务c:0
继续执行任务c:0
继续执行任务c:0
继续执行任务d:0
继续执行任务c:0
继续执行任务d:0
继续执行任务d:0
继续执行任务d:0
继续执行任务c:0
继续执行任务d:0
继续执行任务d:0
误会,嘿嘿。CountDownLatch果然可以在多个线程上添加await。
CountDownLatch能不能在多个线程上添加await?的更多相关文章
- CountDownLatch同步工具--控制多个线程执行顺序
好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行. java.util.concurrent.CountDownLatch ...
- “不支持一个STA线程上针对多个句柄的WaitAll。”的解决方案
一.异常提示 不支持一个 STA 线程上针对多个句柄的 WaitAll. 出错界面如下图: 二.解决方法 先直接上解决方案吧.其实解决方法很简单如下面的代码直接把main函数的[STAThread]属 ...
- 不支持一个 STA 线程上针对多个句柄的 WaitAll
[csharp] view plaincopy using System; using System.Collections.Generic; using System.Windows.Forms; ...
- C#中的线程(上)-入门 分类: C# 线程 2015-03-09 10:56 53人阅读 评论(0) 收藏
1. 概述与概念 C#支持通过多线程并行地执行代码,一个线程有它独立的执行路径,能够与其它的线程同时地运行.一个C#程序开始于一个单线程,这个单线程是被CLR和操作系统(也称为"主线 ...
- 【WPF】在新线程上打开窗口
当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环.直到消息循环结束,应用程序就随即退出.那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗 ...
- 千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死
原文地址:https://www.cnblogs.com/wangchuang/archive/2013/02/20/2918858.html .c# Invoke和BeginInvoke 区别 Co ...
- [WPF]使用CheckAccess检测是否在控件的ui线程上执行
private void Parallel(object sender, RoutedEventArgs e) { Task.Run(() => ChangeColour(Brushes.Red ...
- Python 之并发编程之线程上
一.线程概念 进程是资源分配的最小单位 线程是计算机中调度的最小单位 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都 ...
- Spring @async 方法上添加该注解实现异步调用的原理
Spring @async 方法上添加该注解实现异步调用的原理 学习了:https://www.cnblogs.com/shangxiaofei/p/6211367.html 使用异步方法进行方法调用 ...
随机推荐
- 使用JavaScript输出带有边框的乘法表
在学习JavaScript(以下简称为js)过程中,会遇到输出9*9乘法表的问题,我们都知道利用双重for循环可以很简单的在网页中打印出来,可是你在做的过程中有没有想着给这个乘法表加一点花样呢? 下面 ...
- js问题记录
1.aixos请求响应302重定向时无法获取返回数据, 解决方法:在请求头中添加 headers: { 'X-Requested-With': 'XMLHttpRequest' },
- URI 未注册(设置 | 语言和框架 | 架构和 DTD)
创建xml文件导入资源出错 解决方法:点击左边的小红灯,选择获取外部资源,加载资源即可
- Linux下MySQL基础及操作语法
什么是MySQL? MySQL是一种开源关系数据库管理系统(RDBMS),它使用最常用的数据库管理语言-结构化查询语言(SQL)进行数据库管理.MySQL是开源的,因此任何人都可以根据通用公共许可证下 ...
- time_formatter writeup
攻防世界time_formatter writeup UAF漏洞和命令注入. 前置知识 1.strdup函数 char * __strdup(const char *s) { size_t len = ...
- RHCSA_DAY02
Linux:一切皆文件 分区:/boot:做引导盘 /swap:虚拟内存----最大20gb /data:自己放文件用 /:根分区 - 图形界面: - Ctrl+Shift +号 //调整命令 ...
- Centos配置网络和主机映射
目录 虚拟机网络的三种配置方式 配置虚拟机IP 主机映射问题 配置虚拟机的主机名 虚拟机远程登录 虚拟机网络的三种配置方式 桥接模式:当前虚拟机与主机在同一个局域网下,同一个局域网下的所有电脑都可以访 ...
- Servlet中的HttpServletResponse 类
HttpServletResponse 类的作用: 理解:顾名思义 就是响应客户端的内容, HttpServletResponse 类和 HttpServletRequest ...
- Java Swing 空布局
Swing 空布局 试了盒布局,说实话不太会用,很多地方都没法更加的细节,又翻了翻资料,知道了还有一个空布局,一看,真不错,很适合我这种菜鸡 用坐标就可以完成界面的布局,不错 话不多说,直接代码 pa ...
- 一篇文章搞懂密码学基础及SSL/TLS协议
SSL协议是现代网络通信中重要的一环,它提供了传输层上的数据安全.为了方便大家的理解,本文将先从加密学的基础知识入手,然后展开对SSL协议原理.流程以及一些重要的特性的详解,最后会扩展介绍一下国密SS ...