Cmd Markdown链接

  1. CountDownLatch源码浅析

参考好文:

前言

CountDownLatch用于同步一个或者多个任务,强制他们等待有其他任务执行的一组操作完成。CountDownLatch典型的用法是将一个程序分成n个相互独立的可分解任务,并创建值为n个互相独立的可分解任务,并创建值为n的CountDownLatch。当每一个任务完成时,都会在调用countdown方法将count-1,等待问题被解决的任务调用这个锁的await,将自身拦截等待,直到锁计数为0,才继续往下执行。

前沿知识

CAS

CAS:Compare and Swap 可以翻译成比较并交换。

Excample:

CAS操作包含三个操作数 --- 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值想匹配,那么处理器会自动将该位置值更新为新值;否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。(在CAS的一些特殊情况下将仅返回CAS是否成功,而不提前当前值。)CAS有效地说明了”我认为位置V的值应该为值A,如果等于该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可“。

通常将CAS用于以同步的方式从地址V读取值A,执行多步计算来获得新值B,然后使用CAS将V的值从A改为B。如果V处的值尚未被更改,则CAS操作成功。

线程队列

CountDownLatch是为了实现公平锁,采用了队列的形式,保证先到先执行。

从构造函数CountDownLatch(int count)说起

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

说明: 构造函数创建了一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:

Sync(int count) {
setState(count);
} protected final void setState(int newState) {
state = newState;
}

说明:上面代码中的state就是CountDownLatch的“锁计数器”,每次调用CountDownLatch的countdown()方法都是通过CAS的方式将state减1,直到state为0。

countDown()方法:

public void countDown() {
sync.releaseShared(1);
} public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

说明: countDown -> releaseShared -> tryReleaseShared的作用是释放占用的锁资源;从for(;;) {} 的形式,可以看出其内部是通过自旋的形式,一直尝试释放锁,直到当前锁状态为0或者释放成功。compareAndSetState(c, nextc) 可以看出其是通过CAS的方式保证原子性的释放锁。

private void doReleaseShared() {
for (;;) {
Node h = head;
//头节点不为null且不等于尾节点 即头节点是实际有意义的节点
if (h != null && h != tail) {
int ws = h.waitStatus;
// Node.SIGNAL:waitStatus value to indicate successor's thread needs unparking
// 即:当头节点的状态为-1时,表示需要唤醒后面的进程。
if (ws == Node.SIGNAL) {
// 通过CAS的形式对head的状态进行更新,更新失败就continue,直到成功。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // 唤醒后续的进程
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

说明: doReleaseShared的作用主要是在释放共享锁成功后,通知后面的节点。

await()方法

如果这里多个线程wait且当前共享锁还不为0则这些wait线程的状态如下图:

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)
doAcquireSharedInterruptibly(arg);
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

说明: getState()返回的是当前CountDownLatch对象剩余的共享锁数量。从tryAcquireShared方法的返回值可以看出:当共享锁的数量不为0时,执行doAcquireSharedInterruptibly(arg),即当前调用await的线程需要加入到等待共享锁释放的队列而不是可以立即往下执行时,进入到doAcquireSharedInterruptibly(arg)方法进行等待。

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//由于采用了公平锁,所以要将节点放到队列里,保证先到先执行。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) { //开启自旋模式 一直等待,直到锁被全部释放。
final Node p = node.predecessor();//获取当前节点的前继节点(node.prev)
// 如果当前节点的前继节点(node.prev)等于head 头节点,则表示当前节点是等待队列中
// 的第一个节点。满足 先到先执行的 公平顺序,则尝试释放锁。
if (p == head) {
//再次获取 当前剩余的共享锁数量是否为0,如果为0,则表示可以进行后续的唤醒动作
int r = tryAcquireShared(arg); // (getState() == 0) ? 1 : -1;
if (r >= 0) { // 满足唤醒的条件 开始进行唤醒动作
// 将当前的node设置为head,也就是模拟当前node出队列动作
// 同时唤醒当前出队列的node节点。
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//校验线程是否被打断,如果被打断则抛出异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

说明:

  1. 当有多个线程执行await()方法,且共享锁的数量尚未等于0的情况下,等待队列如下图(0先到 1、2、3依次)

  2. doAcquireSharedInterruptibly方法的执行逻辑如下图,特别要说明的是Thread0 Thread1 Thread2 Thread3内部都并行在执行下图逻辑,不断校验自己的前驱节点是否为head,自身是否为中断。

CountDownLatch源码浅析的更多相关文章

  1. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  2. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. Struts2源码浅析-ConfigurationProvider

    ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...

  4. (转)【深入浅出jQuery】源码浅析2--奇技淫巧

    [深入浅出jQuery]源码浅析2--奇技淫巧 http://www.cnblogs.com/coco1s/p/5303041.html

  5. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  6. Android 手势识别类 ( 三 ) GestureDetector 源码浅析

    前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...

  7. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  8. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. Android手势源码浅析-----手势绘制(GestureOverlayView)

    Android手势源码浅析-----手势绘制(GestureOverlayView)

随机推荐

  1. sass的循环for,while,each

    1. for @for $i from 1 to 10 { .border-#{$i} { border: #{$i}px solid blue; } } 2. while $i: 6; @while ...

  2. Trades FZU - 2281 (大数+贪心)

    This is a very easy problem. ACMeow loves GTX1920. Now he has m RMB, but no GTX1920s. In the next n ...

  3. Apple Tree POJ - 3321 dfs序列构造树状数组(好题)

    There is an apple tree outside of kaka's house. Every autumn, a lot of apples will grow in the tree. ...

  4. 三大linux系统对比

    概述: centos作为服务器部署是第一选择.CentOS去除很多与服务器功能无关的应用,系统简单但非常稳定,命令行操作可以方便管理系统和应用,丰富的帮助文档和社区的支持. ubuntu最佳的应用领域 ...

  5. Spark Streaming 的一些问题

    Spark Streaming 的一些问题,做选型前关注这些问题可以有效的降低使用风险. checkpoint checkpoint 是个很好的恢复机制.但是方案比较粗暴,直接通过序列化的机制写入到文 ...

  6. (译)理解python线程

    看到一篇老外写的线程文章,很赞,零基础都能看懂.先贴在这里,有时间再翻译出来. http://agiliq.com/blog/2013/09/understanding-threads-in-pyth ...

  7. 【BZOJ3998】弦论 [SAM]

    弦论 Time Limit: 10 Sec  Memory Limit: 256 MB[Submit][Status][Discuss] Description 对于一个给定长度为N的字符串,求它的第 ...

  8. 【比赛】STSRM 09

    第一题 题意:n个点,每个点坐标pi属性ai,从右往左将遇到的点向左ai范围内的点消除,后继续扫描. 现可以在扫描开始前提前消除从右往左任意点,问最少消除数(提前+扫描). n,pi,ai<=1 ...

  9. 【NOIP】提高组2012 同余方程

    [算法]扩展欧几里德算法 [题解]学完扩欧就可以随便水了... 转化为不定方程ax-by=1. 因为1且题目保证有解,所以方程有唯一解. 紫书曰:同余方程的一个解其实指的是一个同余等价类. 所以满足x ...

  10. Java 扑克牌发牌

    今天看到这个算法题,http://www.cnblogs.com/xishuai/p/3392981.html ,忍不住自己用Java做了一个. 初始化很重要,所有的52张牌按顺序放入到容器里边,标志 ...