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. HDU5696:区间的价值——题解

    http://acm.hdu.edu.cn/showproblem.php?pid=5696 题面是中文的我就不粘贴过来了…… ———————————————————————— 这题垃圾题!!神tm卡 ...

  2. 在 C Level 用 dlopen 使用 第三方的 Shared Library (.so)

    http://falldog7.blogspot.com/2013/10/android-c-level-dlopen-shared-library-so.html 在 Android 裡,撰寫 JN ...

  3. 【单调队列】【P1776】宝物筛选

    传送门 Description 终于,破解了千年的难题.小FF找到了王室的宝物室,里面堆满了无数价值连城的宝物--这下小FF可发财了,嘎嘎.但是这里的宝物实在是太多了,小FF的采集车似乎装不下那么多宝 ...

  4. 【计数】【UVA11401】 Triangle Counting

    传送门 Description 把1……n这n个数中任取3个数,求能组成一个三角形的方案个数 Input 多组数据,对于每组数据,包括: 一行一个数i,代表前i个数. 输入结束标识为i<3. O ...

  5. Spring源码解析-autowiring自动装配的实现

    IoC容器提供了自动依赖装配的方式,为应用IoC容器提供很大的方便.在自动配置中,不需要显式的去指定Bean属性,只需要配置autowiring属性,IoC容器会根据这个属性配置,使用反射的方式查找属 ...

  6. bug report

    ubuntu 11.10添加eth0:1后重启网卡不显示 eth0:1 http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=324306

  7. 不要在linux上启用net.ipv4.tcp_tw_recycle参数

    不要在linux上启用net.ipv4.tcp_tw_recycle参数 发布于 2015/07/27 莿鸟栖草堂 本文为翻译英文BLOG<Coping with the TCP TIME-WA ...

  8. LightOJ 1093 - Ghajini 线段树

    http://www.lightoj.com/volume_showproblem.php?problem=1093 题意:给定序列,问长度为d的区间最大值和最小值得差最大是多少. 思路:可以使用线段 ...

  9. jenkins修改job默认名字

    ${GIT_BRANCH,fullName="false"}-${BUILD_NUMBER}-${GIT_REVISION,length=12}-${deploy_rollback ...

  10. ssh 远程执行命令简介

    在写这篇博客之前,我google了一堆相关文章,大都是说修改/etc/sudoers,然后NOPASSWD:指定的cmd,但是真心不管用,没有远程虚拟终端这个方法就是浮云,ubuntu10.04 se ...