CountDownLatch原理分析

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有框架服务之后执行。

CountDownLatch使用示例:

首先我们写一个示例,看看怎么使用CountDownLatch工具类

CountDownLatchTest.java

package com.study.thread.juc_thread.base;

import java.util.concurrent.CountDownLatch;

/**
* <p>Description: CountDownLatch 在所有任务结束之前,一个或者多线线程可以一直等待(通过计数器来判断) </p>
* @author duanfeixia
* @date 2019年8月12日
*/
public class CountDownLatchTest { static CountDownLatch countDown = new CountDownLatch(10);//10个线程任务 /**
* <p>Description: 主线程执行任务</p>
* @author duanfeixia
* @date 2019年8月12日
*/
static class BossThread extends Thread{
public void run(){
System.out.println("boss已经到达会议室,共有"+countDown.getCount()+"人参加会议...");
//等待人齐
try {
countDown.await();//进入阻塞队列
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("人已经到齐啦,开会啦...");
}
} /**
* <p>Description:子线程执行任务 </p>
* @author duanfeixia
* @date 2019年6月24日
*/
static class EmployThread extends Thread{ @Override
public void run() {
System.out.println(Thread.currentThread().getName()+"到达会议室,还有"+countDown.getCount()+"人未到");
countDown.countDown();//计数器-1
} } /*测试启动*/
public static void main(String args[]){
new BossThread().start();
int len=(int) countDown.getCount();
for(int i=0;i<len;i++){
new EmployThread().start();
}
}
}

  

测试结果如下:(每次执行的结果都会不一样哦)

CountDownLatch原理解析

CountDownLatch内部依赖Sync实现,而Sync继承AQS。

CountDownLatch主要分析以下三点:

1. 构造方法 (创建CountDownLatch对象时指定线程个数)

2. await()方法的实现 (当前线程计数器为0之前一致等待,除非线程被中断)

3. countDown()方法的实现 (每执行一个线程方法就将计数器减一,当计数为0时 启用当前线程)

CountDownLatch的静态内部类 Sync (这里我们需要特别注意的一点是  Sync 继承了  AbstractQueuedSynchronizer 类, 重要方法会被重写)

 private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; 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;
}
}
}

  

>> CountDownLatch只提供了一个有参构造方法: (参数计数器总量)

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

  

>> await() 方法 [注释: 以下 AQS 均表示 AbstractQueuedSynchronizer ]

初步进入我们会发现调用的是 CountDownLacth 的 await()  -- >>  获取共享锁

 public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

 

acquireSharedInterruptibly()  方法是父类 AQS 中定义的,这里会发现此方法是被final修饰的,无法被重写,但是子类可以重写里面调用的 tyAcquireShared(arg) 方法

    public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

  

先来看看 AQS 中 默认对 tryAcquireShared方法的默认实现

   protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

  

CountDownLatch中的内部类 Sync对 AQS 的 tryAcquireShared方法进行了复写

当前计数器的值为0的时候 返回 1   #获取锁成功,直接返回 线程可继续操作

当前计数器的值不为0的时候 返回 -1.  #获取锁失败 ,开始进入队列中排队等待。接下来就会继续按照 AQS acquireSharedInterruptibly 方法中的逻辑,执行 doAcquireSharedInterruptibly(int arg)

 protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

  

AQS 中 doAcquireSharedInterruptibly(int arg) 实现如下 (该方法为一个自旋方法会尝试一直去获取同步状态)

 /**
* 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);
}
}

这里需要进一步说明的是方法 parkAndCheckInterrupt ,调用 LockSupportpark方法,禁用当前线程

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。

  private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

  

  

>> countDown()方法的实现

CountDownLatch方法中实现如下,方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 -->> 释放共享锁

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

  

AQS中 releaseShared方法的实现如下:(同样 releaseShared方法被final修饰 不能被重写 但是我们CountDownLatch的内部类 Sync重写了 tryReleaseShared方法)

 public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

  

Sync中 tryReleaseShared方法的实现

protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();//获取锁状态
if (c == 0)
return false; //计数器为0的时候 说明释放锁成功 直接返回
int nextc = c-1; //将计数器减一 使用CAS更新计算器的值
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

  

然后执行 AQS 中 releaseShared方法中的 doReleaseShared方法 去释放锁信息

 private void doReleaseShared() {
/*
* 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) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
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;
}
}

  

这里需要特别说明的是 unparkSuccessor(h)方法,调用 LockSupportunpark方法 启动当前线程

  /**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

  

总结:

CountDownLatch内部通过共享锁实现。在创建CountDownLatch实例时,需要传递一个int型的参数:count,该参数为计数器的初始值,也可以理解为该共享锁可以获取的总次数。

当某个线程调用await()方法,程序首先判断count的值是否为0,如果不会0的话则会一直等待直到为0为止。

当其他线程调用countDown()方法时,则执行释放共享锁状态,使count值 – 1。

当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。注意CountDownLatch不能回滚重置。

参考博客地址:http://cmsblogs.com/?p=2253

CountDownLatch原理分析的更多相关文章

  1. Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

    1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...

  2. java并发包&线程池原理分析&锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...

  3. AQS工作原理分析

      AQS工作原理分析 一.大致介绍1.前面章节讲解了一下CAS,简单讲就是cmpxchg+lock的原子操作:2.而在谈到并发操作里面,我们不得不谈到AQS,JDK的源码里面好多并发的类都是通过Sy ...

  4. Redisson 实现分布式锁的原理分析

    写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题.​ 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁, ...

  5. Redisson 实现分布式锁原理分析

    Redisson 实现分布式锁原理分析   写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题.​ 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有 ...

  6. Handler系列之原理分析

    上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...

  7. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

  8. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  9. Android中Input型输入设备驱动原理分析(一)

    转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...

随机推荐

  1. 跟着文档学习gulp1.1安装入门

    Step1:检查是否已经安装了node,npm 和 npX是否正确安装 Step2:安装gulp命令行工具(全局安装gulp) npm install --global gulp-cli Step3: ...

  2. UWP GraphQL数据查询的实现

    1. 缘起 Facebook 的移动应用从 2012 年就开始使用 GraphQL.GraphQL 规范于 2015 年开源,现已经在多种环境下可用,并被各种体量的团队所使用. 在这个链接可以看到更多 ...

  3. 这几种JavaScript语法不要轻易使用,容易出事

    文章目录 12种不宜使用的JavaScript语法 1. == 2. with 3. eval 4. continue 5. switch 贯穿 6. 单行的块结构 7. ++和-- 8. 位运算符 ...

  4. SpringCloud+Eureka+Feign+Ribbon的简化搭建流程和CRUD练习

    作者:个人微信公众号:程序猿的月光宝盒 环境:win10--idea2019--jdk8 1.搭建Eureka服务模块 1.1 新建eureka服务模块(Sping Initializr) 取名为eu ...

  5. Android4.4 RIL短信接收流程分析

    最近有客户反馈Android接收不到短信,于是一头扎进RIL里面找原因.最后发现不是RIL的问题,而是BC72上报短信的格式不对,AT+CNMA=1无作用等几个小问题导致的.尽管问题不在RIL,但总算 ...

  6. docker安装redis 5.0.7并挂载外部配置和数据

    环境 CentOS Linux release 7.7.1908 (Core) 拉取redis 5.0.7 镜像 docker pull redis:5.0.7 创建挂载目录 mkdir -p /ho ...

  7. 使用shell脚本删除30天以前的文件

    #!/bin/bashlocation=/root/rmfind $location -mtime +30 -print | xargs rm -rf //-mtime是距离上一次修改时间 -prin ...

  8. MQ报错2009/2085解决方法

    1.1. 响应2009错误 1.1.1.   涉及协议 MQ,调试回放阶段 1.1.2.   错误信息 完成码2原因为2009 1.1.3.   可能原因 远端MQ连接数不足,拒绝连接 1.1.4.  ...

  9. WPF 后台模拟界面触摸点击

    win32Api提供一种方法,模拟用户触摸点击 InjectTouchInput function InitializeTouchInjection InjectTouchInput 在模拟添加触摸输 ...

  10. 新学期教育教学小学家长会PPT模板推荐

    模版来源:http://ppt.dede58.com/jiaoxuekejian/26569.html