CountDownLatch的实现原理
CountDownLatch是java并发包中辅助并发的工具类,目的是让并发运行的代码在某一个执行点阻塞,直到所有条件都满足,这里的条件就是调用countDown()方法,有点类似计数器的功能。
用法如
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
//如果没有countDown()操作,直接调用await方法则永远不会打印最后一行
countDownLatch.countDown();
countDownLatch.countDown();
countDownLatch.await();
System.out.println("运行结束");
}
构造函数中传入的数字2,表示需要2次countDown()方法调用,否则代码会一直阻塞在await()方法调用处
比较常见的用法如,主线程声明一个CountDownLatch,然后多线程countDown,主线程在等待
public static void testMultiThread() throws InterruptedException {
int threadNum = 2;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
for(int i=0;i<threadNum;i++){
executorService.execute(new Runnable() {
public void run() {
System.out.println("我的任务,打印出一句话");
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("全部任务都结束了,欧耶");
executorService.shutdown();
}
运行结果
我的任务,打印出一句话
我的任务,打印出一句话
全部任务都结束了,欧耶 Process finished with exit code 0
如果把第三行代码修改成
final CountDownLatch countDownLatch = new CountDownLatch(threadNum+1);
那么程序将永远无法打印出
全部任务都结束了,欧耶
以上是这个类的表象行为,那么它是如何在多线程做到这样的功能呢
先来看看它的类结构
CountDownLacth中,有6个public的方法,一个内部私有类Sync,及一个Sync实例的变量sync
Sync是这个类的关键,它保证了countDown(),await()方法在多线程场景下可以保证countDownLatch的可见性(正常的同步)
我们先来自己实现一个CountDownLacth类,使用synchronized关键字实现
MyCountDownLatch模拟了countDown和await方法,通过synchronized和私有变量state来达到这个目的。synchronized的劣势在于锁机制完全互斥,并发量高时性能下降比较明显,无法维持常态化的性能(JDK 5)。因此CountDownLatch以及并发包中的类都采用了取巧的方式,通过线程自旋来追求线程响应时间,而不是让线程只能一直等待锁被释放再竞争。1.6之后,synchronized的性能和ReentrantLock的性能其实已经相当,偏向锁也改进了一个线程重复获取锁时不需要cpu切换上下文。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//初始化总的countdown次数值,这个操作涉及内存语义 volatile写
Sync(int count) {
setState(count);
} int getCount() {
return getState();
}
//尝试获取共享锁,如果可以获取锁,需要返回对应的状态值,这个方法总是由执行获取的线程调用
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//尝试释放共享锁,这个方法总是由执行释放的线程调用
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
CountDownLatchd countDown方法会调用
Sync的tryReleaseShared去将计数器-1
public void countDown() {
sync.releaseShared(1);
}
AbstractQueuedSynchronizer类方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} AbstractQueuedSynchronizer类方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
} AbstractQueuedSynchronizer类方法
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
await()方法Sync的tryAcquireShared方法不断判断计数器值为0,方法返回正数,await方法直接返回,亦即是获取了锁
AbstractQueuedSynchronizer类是concurrent包的一个基础类,基于它,我们可以实现并发安全的诸多功能,例如CountDownLacth
/**
* The synchronization state.
*/
private volatile int state;
AbstractQueuedSynchronizer类有一个volatile的变量,CountDownLacth中的Sync类,getState()方法其实就是获取它的值
由于volatile关键字的特殊内存语义,当它被修改时,将本地高速缓存中的值写到主存中去,当它的值被读取时,会把本地缓存中state置为无效,到主存获取值,因此每个线程都能获取到他最新的值
countDownLatch.countDown();
-> sync.releaseShared(1);
-> public final boolean releaseShared(int arg) {
//countdown时,尝试判断当前状态,如果状态已经可以完全释放锁时,进行释放锁操作
if (tryReleaseShared(arg)) {
doReleaseShared(); //释放共享锁
return true;
}
return false;
} /**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/* 判断是否已经有线程(node)在等待,如果在等待,则从head开始,寻找后继节点,并唤醒他们
* 直到最后head 为null或head == tail,才退出,退出时,已然唤醒了所有需要唤醒的线程
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head; //等待获取锁的队列
if (h != null && h != tail) { //head == tail 时,是一个特殊情况,是第一个node入队时的临时状态,此时head节点上没有等待的线程
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); //head唤醒后继线程
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //尝试设置waitStatus状态为PROPAGATE
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
countDownLatch.await();
-> sync.acquireSharedInterruptibly(1);
->
/**
* Acquires in shared mode, aborting if interrupted. Implemented
* by first checking interrupt status, then invoking at least once
* {@link #tryAcquireShared}, returning on success. Otherwise the
* thread is queued, possibly repeatedly blocking and unblocking,
* invoking {@link #tryAcquireShared} until success or the thread
* is interrupted.
* @param arg the acquire argument.
* This value is conveyed to {@link #tryAcquireShared} but is
* otherwise uninterpreted and can represent anything
* you like.
* @throws InterruptedException if the current thread is interrupted
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //尝试判断共享锁状态,即判断state是否为0了,如果已经为0,表示期待的状态已经达到了,锁的状态已经标识不需等待了,直接返回
doAcquireSharedInterruptibly(arg); //为0后
} /**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //当前线程装入node,此时等待队列不为空了
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor(); //当前线程的前继线程
if (p == head) { //前继为head
int r = tryAcquireShared(arg); //判断state状态,是否已经ok
if (r >= 0) { //state值为0时,r == 1
setHeadAndPropagate(node, r); //当前node设置为head,尝试获取下一个node
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //尝试park(阻塞)当前线程,有点像进入wait状态,处理器可能不会分配时间
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里需要说明一下,node的waitStatus是在何时被设置为Node.SIGNAL状态的
shouldParkAfterFailedAcquire方法中,pred参数为当前node的前继node,方法一进来就判断了pred.waitStatus是否为Node.SIGNAL,如果已经是这个状态了就直接返回
ws>0表示CANCELLED状态,因为其他状态都是<=0的,此时遍历的将这种无效的node去掉;如果ws <= 0,就将pred的状态变成Node.SIGNAL,并返回false;这里可以看到,如果前缀node已经是SIGNAL状态
就会park当前节点线程,如果,前缀node是未cancel状态,就设置为SIGNAL状态。这样,在调用await时,如果无法立即返回,就会将当前线程阻塞,并设置前置node状态为SIGNAL。这也对应了releaseShared
中的Node.SIGAL状态的判断。await()是从tail -> head方向做的;countdown(releaseShared)的方向是从head -> tail的,这样,等待锁的线程不断判断前缀node,释放锁的线程不断更新head -> tail
状态,随着head -> tail状态更新完毕,await等待锁也等到了p == head,返回true
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Java的LockSupport.park()实现分析 参见博客: https://blog.csdn.net/hengyunabc/article/details/28126139
附waitStatus的各种状态 /**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block. 线程的后继线程正/已被阻塞,当该线程release或cancel时,要唤醒这个后继线程(unpark)
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks. 由于timeout或线程被中断时的状态
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.) 表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened. 传播共享锁,只能在head上设置这个状态
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
CountDownLatch的实现原理的更多相关文章
- Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析
1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...
- 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理
1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...
- 并发——深入分析CountDownLatch的实现原理
一.前言 最近在研究java.util.concurrent包下的一些的常用类,之前写了AQS.ReentrantLock.ArrayBlockingQueue以及LinkedBlockingQu ...
- Java并发包中CountDownLatch的工作原理、使用示例
1. CountDownLatch的介绍 CountDownLatch是一个同步工具,它主要用线程执行之间的协作.CountDownLatch 的作用和 Thread.join() 方法类似,让一些线 ...
- Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例
概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...
- Java并发包5--同步工具CountDownLatch、CyclicBarrier、Semaphore的实现原理解析
前言: JUC中提供了很多同步工具类,比如CountDownLatch.CyclicBarrier.Semaphore等,都可以作用同步手段来实现多线程之间的同步效果 一.CountDownLatch ...
- CountDownLatch原理详解
介绍 当你看到这篇文章的时候需要先了解AQS的原理,因为本文不涉及到AQS内部原理的讲解. CountDownLatch是一种同步辅助,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的 ...
- Java并发系列[7]----CountDownLatch源码分析
CountDownLatch(闭锁)是一个很有用的工具类,利用它我们可以拦截一个或多个线程使其在某个条件成熟后再执行.它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须 ...
- CountDownLatch 使用说明
CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成. CountDownLatch的用法非常简单,下 ...
随机推荐
- C++ stl 运用(深层)
1.multiset(set差不多) (1)erase删除,删除指针和键值是不同的. 键值的话是删除所有,指针的话是那个位置的值. (2)统计单个键值个数. (3)对于q.begin(),q.end( ...
- sudo使用详细讲解
1.原因:让普通用户具有root用户的权限通过sudo执行的命令都会存在日志里面2.用法1.sudo -l 列出当前用户有哪些sudo权限 2.visudo -c 检查语法是否错误 3.visudo ...
- ASP.NET Core的身份认证框架IdentityServer4(5)- 包和构建
包和构建 IdentityServer有许多nuget包 IdentityServer4 nuget | github 包含IdentityServer核心对象模型,服务和中间件. 仅支持内存配置和用 ...
- ASP.NET Core中的OWASP Top 10 十大风险-失效的访问控制与Session管理
不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: https://dotnetcoretutorials.com/201 ...
- python添加自定义cookies
import cookielib,urllib2 class AddCookieHandler(urllib2.BaseHandler): def __init__(self,cookieValue) ...
- Easy UI下拉列表默认选中(多行)与为文本框赋值
1.为单行文本框赋值 var data2 = $('#LoadArea').combobox("getData"); if (data2) { $('#id).combobox(' ...
- C#截取当前活动窗体的图片
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- [转载] Storm:最火的流式处理框架
转载自http://www.cnblogs.com/langtianya/p/5199529.html 伴随着信息科技日新月异的发展,信息呈现出爆发式的膨胀,人们获取信息的途径也更加多样.更加便捷,同 ...
- [转载] 详述三种现代JVM语言--Groovy,Scala和Clojure
转载自http://www.tuicool.com/articles/jYzuAv和http://www.importnew.com/1537.html 在我与Martin Fowler曾经合作呈现的 ...
- Python之函数返回多个值
#!/usr/bin/env python26 #-*- coding:utf-8-*- def test(): a = 10 b = 20 return a,b #返回一个元组 atuple= te ...