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. Web基础了解版01-html-css

    HTML 网页构成 摘要 说明 结构(HTML) HTML是网页内容的载体.内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字.图片.视频等. 表现(CSS) CSS样式是表现.就像网页的 ...

  2. salt python msgpack.exceptions.

    msgpack.exceptions.UnpackValueError: 'utf-8' codec can't decode byte 0x82 in position 22: invalid st ...

  3. ClassNotFoundException------IDEA下的一种原因

    由于直接复制文件而未经过IDE造成次异常,需要修改程序入口:

  4. uni-app自定义app端的扫码界面

    记得当时是在西班牙有这样的一个需求,需要自定义扫码页面,还需要加上西班牙文,当时是在一个组件里面找到了这样的一个方法,全文大部分使用的app端的Native里面的方法,记录一下,跑路了项目代码要删库了 ...

  5. Rancher 2.3实现K8S一键式升级!再也不用同步升级Rancher啦!

    在Rancher 2.3之前,Rancher的新版本总是随着Kubernetes的新版本一起发布,如果你想要使用最新版本的Kubernetes,那么你需要先升级Rancher才能使用.Rancher ...

  6. Sqlite—删除语句(Delete)

    SQLite 的 DELETE 语句用于删除表中已有的记录.可以使用带有 WHERE 子句的 DELETE 查询来删除选定行,否则所有的记录都会被删除. SQLite 要清空表记录,只能使用Delet ...

  7. 求连通块的面积 - BFS、DFS实现

    本文以Leetcode中695.岛屿的最大面积题目为基础进行展开(题目

  8. HttpRunner学习2--用例格式和简单使用

    前言 HttpRunner中,测试用例支持两种文件格式:YAML 和 JSON.两种格式的用例是完全等价的,对于相同的信息内容,使用 YAML /JSON 得到的测试结果和报告也是一致的. 本人环境: ...

  9. JS---DOM---点击操作---part2---14个案例

    案例1:点击按钮禁用文本框 <input type="button" value="禁用文本框" id="btn" /> < ...

  10. Computer: Use the mouse to open the analog keyboard

    Xx_Introduction Please protection,respect,love,"China's Internet Security Act"! For learni ...