深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)[转]
此小节介绍几个与锁有关的有用工具。
闭锁(Latch)
闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
CountDownLatch是JDK 5+里面闭锁的一个实现,允许一个或者多个线程等待某个事件的发生。CountDownLatch有一个正数计数器,countDown方法对计数器做减操作,await方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。
CountDownLatch的API如下。
- public void await() throws InterruptedException
- public boolean await(long timeout, TimeUnit unit) throws InterruptedException
- public void countDown()
- public long getCount()
其中getCount()描述的是当前计数,通常用于调试目的。
下面的例子中描述了闭锁的两种常见的用法。
package xylz.study.concurrency.lock;
import java.util.concurrent.CountDownLatch;
public class PerformanceTestTool {
public long timecost(final int times, final Runnable task) throws InterruptedException {
if (times <= 0) throw new IllegalArgumentException();
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch overLatch = new CountDownLatch(times);
for (int i = 0; i < times; i++) {
new Thread(new Runnable() {
public void run() {
try {
startLatch.await();
//
task.run();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} finally {
overLatch.countDown();
}
}
}).start();
}
//
long start = System.nanoTime();
startLatch.countDown();
overLatch.await();
return System.nanoTime() - start;
}}
在上面的例子中使用了两个闭锁,第一个闭锁确保在所有线程开始执行任务前,所有准备工作都已经完成,一旦准备工作完成了就调用startLatch.countDown()打开闭锁,所有线程开始执行。第二个闭锁在于确保所有任务执行完成后主线程才能继续进行,这样保证了主线程等待所有任务线程执行完成后才能得到需要的结果。在第二个闭锁当中,初始化了一个N次的计数器,每个任务执行完成后都会将计数器减一,所有任务完成后计数器就变为了0,这样主线程闭锁overLatch拿到此信号后就可以继续往下执行了。
根据前面的happend-before法则可以知道闭锁有以下特性:
内存一致性效果:线程中调用
countDown()之前的操作 happen-before 紧跟在从另一个线程中对应await()成功返回的操作。
在上面的例子中第二个闭锁相当于把一个任务拆分成N份,每一份独立完成任务,主线程等待所有任务完成后才能继续执行。这个特性在后面的线程池框架中会用到,其实FutureTask就可以看成一个闭锁。后面的章节还会具体分析FutureTask的。
同样基于探索精神,仍然需要“窥探”下CountDownLatch里面到底是如何实现await*和countDown的。
首先,研究下await()方法。内部直接调用了AQS的acquireSharedInterruptibly(1)。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
前面一直提到的都是独占锁(排它锁、互斥锁),现在就用到了另外一种锁,共享锁。
所谓共享锁是说所有共享锁的线程共享同一个资源,一旦任意一个线程拿到共享资源,那么所有线程就都拥有的同一份资源。也就是通常情况下共享锁只是一个标志,所有线程都等待这个标识是否满足,一旦满足所有线程都被激活(相当于所有线程都拿到锁一样)。这里的闭锁CountDownLatch就是基于共享锁的实现。
闭锁中关于AQS的tryAcquireShared的实现是如下代码(java.util.concurrent.CountDownLatch.Sync.tryAcquireShared):
public int tryAcquireShared(int acquires) {
return getState() == 0? 1 : -1;
}
在这份逻辑中,对于闭锁而言第一次await时tryAcquireShared应该总是-1,因为对于闭锁CountDownLatch而言state的值就是初始化的count值。这也就解释了为什么在countDown调用之前闭锁的count总是>0。
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
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
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
break;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
// Arrive here only if interrupted
cancelAcquire(node);
throw new InterruptedException();
}
上面的逻辑展示了如何通过await将所有线程串联并挂起,直到被唤醒或者条件满足或者被中断。整个过程是这样的:
这里有一点值得说明下,设置头结点并唤醒继任节点setHeadAndPropagate。由于前面tryAcquireShared总是返回1或者-1,而进入setHeadAndPropagate时总是propagate>=0,所以这里propagate==1。后面唤醒继任节点操作就非常熟悉了。
private void setHeadAndPropagate(Node node, int propagate) {
setHead(node);
if (propagate > 0 && node.waitStatus != 0) {
Node s = node.next;
if (s == null || s.isShared())
unparkSuccessor(node);
}
}
从上面的所有逻辑可以看出countDown应该就是在条件满足(计数为0)时唤醒头结点(时间最长的一个节点),然后头结点就会根据FIFO队列唤醒整个节点列表(如果有的话)。
从CountDownLatch的countDown代码中看到,直接调用的是AQS的releaseShared(1),参考前面的知识,这就印证了上面的说法。
tryReleaseShared中正是采用CAS操作减少计数(每次减-1)。
public boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
整个CountDownLatch就是这个样子的。其实有了前面原子操作和AQS的原理及实现,分析CountDownLatch还是比较容易的。
深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)[转]的更多相关文章
- 深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)
此小节介绍几个与锁有关的有用工具. 闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻 ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题[转]
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的一点 ...
- 深入浅出 Java Concurrency (6): 锁机制 part 1 Lock与ReentrantLock
前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念 ...
- 深入浅出 Java Concurrency (9): 锁机制 part 4 锁释放与条件变量 (Lock.unlock And Condition)
本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...
- 深入浅出 Java Concurrency (9): 锁机制 part 4[转]
本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...
- 深入浅出 Java Concurrency (6): 锁机制 part 1[转]
前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念和设 ...
- 深入浅出 Java Concurrency (7): 锁机制 part 2 AQS
在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复杂的一个基础类:java.util.concurrent.locks.AbstractQueuedSynchronizer ...
- 深入浅出 Java Concurrency (7): 锁机制 part 2 AQS[转]
在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复杂的一个基础类:java.util.concurrent.locks.AbstractQueuedSynchronizer. ...
随机推荐
- C++Builder中注册表的操作
僮骶头浅5募虻チ耍旅嫖揖鸵砸桓鍪道此得鱐Registry类的用法.首先,先介绍一下TRegistry的属性和方法:TRegistry类一共有四个属性.属性 类型 描述CurrentKey int ...
- P1977 出租车拼车
P1977 出租车拼车 题目背景 话说小 x 有一次去参加比赛,虽然学校离比赛地点不太远,但小 x 还是想坐 出租车去.大学城的出租车总是比较另类,有“拼车”一说,也就是说,你一个人 坐车去,还是一堆 ...
- android GPS: code should explicitly check to see if permission is available
转载的,感谢作者,由于我找了很久才找到这个解决方法,因此我自己再转一遍 原文链接 https://blog.csdn.net/qinwendou/article/details/77849048 if ...
- MySQL 调优/优化的 101 个建议!
原文:http://www.monitis.com/blog/101-tips-to-mysql-tuning-and-optimization/ MySQL是一个强大的开源数据库.随着MySQL上的 ...
- vue组件间通信用例
父组件传值给子组件 -- 以封装公用slide组件为例 父组件 <template> <section class="banner"> <slide ...
- iOS逆向系列-Mach-O文件
概述 Mach-O是Mach object的缩写,是Mac\iOS上用于存储程序.库的标准格式. 常见的Mach-O文件 属于Mach-O格式的文件类型有. 可以在xnu源码中,查看到Mach-O格式 ...
- 3_基本框架_VMXON
原理参考 3卷 23.7节等 本节实施流程参考Intel手册: 3卷 31.5节 1 vt整体框架; 首先 开锁: 1 开启 Cr4.[VMXE]: 上一节,检测了 VMX 需要的环境:最后一个 CR ...
- Redis理解和使用
摘抄并用于自查笔记 1. Redis简介 我们日常Java Web开发,一般使用数据库进行存储,在数据量较大的情况下,单一使用数据库保存数据的系统会因为面向磁盘,磁盘读写速度比较慢而存在严重的性能弊端 ...
- art-template官方文档
http://aui.github.io/art-template/zh-cn/docs/
- 接口测试——postman
一.接口测试的准备工作 做接口测试之前需要有接口文档,请求参数,返回参数 二.使用postman进行接口测试 1.get请求 ①get请求可以直接在URL后面添加问号加参数,不需要使用工具来测试接口: ...