三沣开发知识 购物 网址 游戏 小说 歌词 地图 快照 股票 美女 新闻 笑话 | 汉字 软件 日历 阅读 下载 图书馆 开发 租车 短信 China
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
vbs/VBScript DOS/BAT hta htc python perl 游戏相关 VBA 远程脚本 ColdFusion ruby专题
autoit seraphzone PowerShell linux shell Lua Golang Erlang 其它教程 CSS/HTML/Xhtml
html5 CSS XML/XSLT Dreamweaver教程 经验交流 开发者乐园 Android开发资料
站长资讯 .NET新手 ASP.NET C# WinForm Silverlight WCF CLR WPF XNA VisualStudio ASP.NET-MVC .NET控件开发 EntityFramework WinRT-Metro Java C++ PHP Delphi Python Ruby C语言 Erlang Go Swift Scala R语言 Verilog 其它语言 架构设计 面向对象 设计模式 领域驱动 Html-Css JavaScript jQuery HTML5 SharePoint GIS技术 SAP OracleERP DynamicsCRM K2 BPM 信息安全 企业信息 Android开发 iOS开发 WindowsPhone WindowsMobile 其他手机 敏捷开发 项目管理 软件工程 SQLServer Oracle MySQL NoSQL 其它数据库 Windows7 WindowsServer Linux
  IT知识库 -> Java -> Java CountDownLatch解析(下) -> 正文阅读
 

[Java]Java CountDownLatch解析(下)

Java CountDownLatch解析(下) 写在前面的话
在上一篇CountDownLatch解析中,我们了解了CountDownLatch的简介、CountDownLatch实用场景、CountDownLatch实现原理中的await()方法,
接下来我们接着来了解它的countDown()方法以及它的示例和优缺点。
CountDownLatch实现原理
二、CountDownLatch.countDown() 
  关于同步队列那点事
当部分线程调用await()方法后,它们在同步队列中被挂起,然后自省的检查自己能否满足醒来的条件(还记得那个条件吗?1、state为0,2、该节点为头节点),
如果满足它将被移除头节点,并将下一个节点设置为头节点。具体如下图:

这个Node是AbstractQueuedSynchronizer(下文简称AQS)中的静态内部类,其中它又有两个属性:

volatile Node prev;

volatile Node next;

volatile的prev指向上一个node节点,volatile的next指向下一个node节点。当然如果是头节点,那么它的prev为null,同理尾节点的next为null。

private transient volatile Node head;

private transient volatile Node tail;

head和tail是AQS中的属性,它们用来表示同步队列的头节点和尾节点。(有兴趣的同学可以引申看一下 transident 这个关于序列化关键字,这里不展开啦=_=)
了解了同步队列后,接着我们开始线程调用countDown()方法后,到底发生了什么事?这儿咱们可以想象一下,可能计数值会减去1,而且还会判断state是不是等于0,
如果不等于0,这个线程就继续往下走了;如果等于0,那么它可能还需要去叫醒这群挂起的线程。
到底是不是这样呢,别方,跟着笔者一起继续来扒源码。

public void countDown() {
   sync.releaseShared(1);
}

当调用CountDownLatch.countDown()后,它转而调用了Sync这个内部类实例的releaseShared()方法。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;     //退出该方法
    }
    return false;        //退出该方法
}

在Sync类中并没有releaseShared()方法,所以应该是继承与AQS,咱们看到AQS这个方法中,退出该方法的只有两条路。tryReleaseShared(arg)条件为真执行一个doReleaseShared()退出;条件为假直接退出。
类比我们的猜测,这个条件很有可能就是state是否为0的判断。。。接着来看。

protected boolean tryReleaseShared(int releases) {
    for (;;) {//死循环
        int c = getState();// 获取主存中的state值
        if (c == 0) //state已经为0 直接退出
            return false;
        int nextc = c-1; // 减一 准备cas更新该值
        if (compareAndSetState(c, nextc)) //cas更新
            return nextc == 0; //更新成功 判断是否为0 退出;更新失败则继续for循环,直到线程并发更新成功
    }
}

看到这儿四不四灵光从脑子喷涌而出啦。我们的猜测是正确的!

private void doReleaseShared() {
    for (;;) {//又是一个死循环
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//如果当前节点是SIGNAL意味着,它正在等待一个信号,或者说它在等待被唤醒,因此做两件事,1是重置waitStatus标志位,2是重置成功后,唤醒下一个节点。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);
            }else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
                continue;                
        }
        if (h == head)                   
            break;
    }
}

同学们(敲黑板...),我们为啥要执行这个方法呀,因为state已经为0啦,我们该将同步队列中的线程状态设置为共享状态(Node.PROPAGATE,默认状态ws == 0),并向后传播,实现状态共享。
这就是为啥方法名有个shared,因为这个共享状态需要传播下去,而不是一个节点(线程)独占。看看这个死循环,退出的路只有一条,那就是h==head,即该线程是头节点,且状态为共享状态。
 这里再啰嗦一句,可能读者会问,state已经等于0了,我们也通过循环的方式把头节点的状态设置为共享状态,但是它怎么醒过来的呢?既然这样,那我们再看一次上一篇中的代码

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;// 计数值为0 并且为头节点 跳出循环
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();// 响应打断 跳出循环
        }
    } finally {
        if (failed)
            cancelAcquire(node);// 如果是打断退出的 则移除同步队列节点
    }
}

这个就是在同步队列中挂起的线程,它们自旋的形式查看自己是否满足条件醒来(state==0,且为头节点),如果成立将调用setHeadAndPropagate这个方法

 private void setHeadAndPropagate(Node node, int propagate) {
     Node h = head; // Record old head for check below
     setHead(node);
     if (propagate > 0 || h == null || h.waitStatus < 0 ||
         (h = head) == null || h.waitStatus < 0) {
         Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

这个方法是将当前节点的下一个节点设置为头节点,且它也调用了doReleaseShared这个方法,我们刚才也说了,这个方法就是将头节点设置为共享状态的,由此,共享状态传播下去。
至此,CountDownLatch实现原理中的countDown()方法剖析结束。
CountDownLatch示例
废话不多说,直接上代码:

package com.test.demo;

import java.util.concurrent.CountDownLatch;

/** 
* @Title: TestCountDownLatch.java
* @Describe:
* @author: Mr.Yanphet 
* @Email: mr_yanphet@163.com
* @date: 2016年9月18日 上午11:22:42  
* @version: 1.0 
*/
public class TestCountDownLatch {
    
    private static final int taskNum = 5;
    
    static class MyRunnable implements Runnable {
        
        private int num;
        
        private CountDownLatch cdl;
        
        public MyRunnable(int num, CountDownLatch cdl){
            this.num = num;
            this.cdl = cdl;
        }
        
        public void run() {
            System.out.println("第" + num + "个线程开始执行任务...");
            try {
                Thread.sleep(5 * 1000); // 模拟任务耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第" + num + "个线程任务执行结束...");
            cdl.countDown();
        }
    }
    
    public static void main(String[] args) {
        CountDownLatch cdl = new CountDownLatch(taskNum);
        for (int i = 1; i <= taskNum; i++) {
            MyRunnable mr = new MyRunnable(i, cdl);
            Thread t = new Thread(mr);
            t.start();
        }
        System.out.println("等待其他线程完成任务才继续执行...");
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("其他线程完成任务,主线程开始执行...");
        System.out.println("主线程任务完成,整个任务进度完成...");
    }

}

执行结果如下:

第4个线程开始执行任务...
第5个线程开始执行任务...
第3个线程开始执行任务...
第2个线程开始执行任务...
第1个线程开始执行任务...
等待其他线程完成任务才继续执行...
第4个线程任务执行结束...
第2个线程任务执行结束...
第1个线程任务执行结束...
第3个线程任务执行结束...
第5个线程任务执行结束...
其他线程完成任务,主线程开始执行...
主线程任务完成,整个任务进度完成...

主线程等待5个子线程执行完任务,才继续往下执行自己的任务。
CountDownLatch的优缺点
优点:
CountDownLatch的优点毋庸置疑,对使用者而言,你只需要传入一个int型变量控制任务数量即可,至于同步队列的出队入队维护,state变量值的维护对使用者都是透明的,使用方便。
缺点:
CountDownLatch设置了state后就不能更改,也不能循环使用。
以上就是关于CountDownLatch学习的全部内容。因为笔者也是菜鸟,所以站在菜鸟的角度分析源码,难免重复啰嗦。如有任何问题,希望大家指正。谢谢~~~
  Java 最新文章
初入山门,需记门规
java 操作本地数据库 mysql
spring boot 整合 mybatis
单表(SSM、SpringBoot、SpringCloud、Free
Mybatis框架中Mapper动态代理方式
IDEA报错:Error starting ApplicationCont
Java内存管理原理及内存区域详解
结合ThreadLocal来看spring事务源码,感受下
JDBC与javaBean知识
Java—恶心的java.lang.NumberFormatExcept
上一篇文章      下一篇文章      查看所有文章
加:2016-09-18 16:57:19  更:2017-05-15 11:18:24 
 
技术频道: 站长资讯 .NET新手区 ASP.NET C# WinForm Silverlight WCF CLR WPF XNA Visual Studio ASP.NET MVC .NET控件开发 Entity Framework WinRT/Metro Java C++ PHP Delphi Python Ruby C语言 Erlang Go Swift Scala R语言 Verilog 其它语言 架构设计 面向对象 设计模式 领域驱动设计 Html/Css JavaScript jQuery HTML5 SharePoint GIS技术 SAP Oracle ERP Dynamics CRM K2 BPM 信息安全 企业信息化其他 Android开发 iOS开发 Windows Phone Windows Mobile 其他手机开发 敏捷开发 项目与团队管理 软件工程其他 SQL Server Oracle MySQL NoSQL 其它数据库 Windows 7 Windows Server Linux
脚本语言: vbs/VBScript DOS/BAT hta htc python perl 游戏相关 VBA 远程脚本 ColdFusion ruby专题 autoit seraphzone PowerShell linux shell Lua Golang Erlang 其它教程
网站开发: CSS/HTML/Xhtml html5 CSS XML/XSLT Dreamweaver教程 经验交流 开发者乐园 Android开发资料
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture 股票 租车
生肖星座 三丰软件 视频 开发 短信 中国文化 网文精选 搜图网 美图 阅读网 多播 租车 短信 看图 日历 万年历 2017年12日历
2017-12-17 18:01:22
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT知识库