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)说起

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

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

  1. Sync(int count) {
  2. setState(count);
  3. }
  4. protected final void setState(int newState) {
  5. state = newState;
  6. }

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

countDown()方法:

  1. public void countDown() {
  2. sync.releaseShared(1);
  3. }
  4. public final boolean releaseShared(int arg) {
  5. if (tryReleaseShared(arg)) {
  6. doReleaseShared();
  7. return true;
  8. }
  9. return false;
  10. }
  11. protected boolean tryReleaseShared(int releases) {
  12. for (;;) {
  13. int c = getState();
  14. if (c == 0)
  15. return false;
  16. int nextc = c-1;
  17. if (compareAndSetState(c, nextc))
  18. return nextc == 0;
  19. }
  20. }

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

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

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

await()方法

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

  1. public void await() throws InterruptedException {
  2. sync.acquireSharedInterruptibly(1);
  3. }
  4. public final void acquireSharedInterruptibly(int arg)
  5. throws InterruptedException {
  6. if (Thread.interrupted()) // 判断线程的状态,如果线程已经状态 就抛出异常
  7. throw new InterruptedException();
  8. if (tryAcquireShared(arg) < 0)
  9. doAcquireSharedInterruptibly(arg);
  10. }
  11. protected int tryAcquireShared(int acquires) {
  12. return (getState() == 0) ? 1 : -1;
  13. }

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

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

说明:

  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. vue中使用 contenteditable 制作输入框并使用v-model做双向绑定

    <template> <div class="div-input" :class="value.length > 0 ? 'placeholder ...

  2. Codeforces Round #331 (Div. 2) A

    A. Wilbur and Swimming Pool time limit per test 1 second memory limit per test 256 megabytes input s ...

  3. FileProvider记录下

    Mark下FileProvider,阿里巴巴Android开发手册有如下要求:[强制]应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用FileProvider. 知识点记录:1. An ...

  4. C++虚基类的初始化

    #include<iostream> using namespace std; class Base{ public: Base(int sa) { a=sa; cout<<& ...

  5. Git版本管理1-安装配置和同步

    原文载于youdaonote,有图片: http://note.youdao.com/share/?id=79a2d4cae937a97785bda7b93cbfc489&type=note ...

  6. rank() within group用法【转】

    参考:http://www.itpub.net/thread-241824-1-1.html  http://blog.itpub.net/13379967/viewspace-481811/ ) w ...

  7. ITEXT5 Font 'd:\SIMSUN.TTC' with 'Identity-H' is not recognized.

    用 itextsharp 制作PDF文件的时候发生错误 Font 'd:\SIMSUN.TTC' with 'Identity-H' is not recognized. 原本是 BaseFont b ...

  8. Android中禁止SlidingPaneLayout的侧滑功能

    Android中使用android.support.v4.widget.SlidingPaneLayout实现侧滑功能的时候,可能出现滑动屏幕时与SlidingPaneLayout的侧滑发生冲突,查看 ...

  9. Spring Security 集成CAS实现单点登录

    参考:http://elim.iteye.com/blog/2270446 众所周知,Cas是对单点登录的一种实现.本文假设读者已经了解了Cas的原理及其使用,这些内容在本文将不会讨论.Cas有Ser ...

  10. Lua中调用C++方法

    目前项目,使用了Lua脚本,至于使用Lua的好处不再赘述了.于是对Tolua做了一些小小的学习,总结一下吧. 主要说一下如何在Lua中调用C++方法. Lua调用C++的桥梁,是tolua.tolua ...