深入浅出 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. ...
随机推荐
- vue中axios使用封装
一.在main.js导入 // 引入axios,并加到原型链中 import axios from 'axios'; Vue.prototype.$axios = axios; import QS f ...
- Android_Gallery(画廊)
转:http://blog.csdn.net/tianjf0514/article/details/7521398 Gallery是画廊的意思,可以实现图片的浏览功能. 主要内容 Gallery控件的 ...
- SpringCloud学习笔记(九):SpringCloud Config 分布式配置中心
概述 分布式系统面临的-配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务.由于每个服务都需要必要的配置信息才能运行,所以一套集中式的.动 ...
- Vue.js Ajax(axios)
Vue.js 2.0 版本推荐使用 axios 来完成 ajax 请求. Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中. Github开源地址: ht ...
- timestamp的自动更新 ON UPDATE CURRENT_TIMESTAMP
最近有一个关于MySQL版本升级的事,涉及到一些关于时间类型的细节问题需要查明,因此到官网找到相关文章,翻出来比较方便自己理解,博客这里也贴一下. 参考官网网址: https://dev.mysql. ...
- Spring+SpringMVC+Mybatis搭建的网站的处理流程总结
最近学习了如何使用SSM框架搭建网站,以前没用过框架,第一次使用,总结一下自己对框架处理流程的理解
- [USACO2005 nov] Grazing on the Run【区间Dp】
Online Judge:bzoj1742,bzoj1694 Label:区间Dp 题目描述 John养了一只叫Joseph的奶牛.一次她去放牛,来到一个非常长的一片地,上面有N块地方长了茂盛的草.我 ...
- array_map、array_walk、array_filter三个函数的区别
array_walk --- 使自定的函数能处理数组的每个元素 bool array_walk ( array &array, callback funcname [, mixed userd ...
- 小米手机 DELETE_FAILED_INTERNAL_ERROR Error while Installing APKs
手机:小米2s,MIUI 9 7.11.16 开发版 手机已处于开发者模式,启用了USB调试,已使用USB线连接了手机,在Android Studio 工具栏点击 "Run ‘app’(Sh ...
- Invalid prop: type check failed for prop "maxlength". Expected Number, got String.
1.项目中,使用element-ui的input表单的maxlength属性报错 2.使用场景: <el-input v-model="fname" maxle ...