多线程之CountDownLatch的用法及原理笔记
前言-CountDownLatch是什么?
CountDownLatch是具有synchronized机制的一个工具,目的是让一个或者多个线程等待,直到其他线程的一系列操作完成。
CountDownLatch初始化的时候,需要提供一个整形数字,数字代表着线程需要调用countDown()方法的次数,当计数为0时,线程才会继续执行await()方法后的其他内容。
CountDownLatch(int count);
对象中的方法
getCount:
返回当前的计数count值,
public void countDown()
调用此方法后,会减少计数count的值。
递减后如果为0,则会释放所有等待的线程
public void await()
throws InterruptedException
调用CountDownLatch对象的await方法后。
会让当前线程阻塞,直到计数count递减至0。
如果当前线程数大于0,则当前线程在线程调度中将变得不可用,并处于休眠状态,直到发生以下两种情况之一:
1、调用countDown()方法,将计数count递减至0。
2、当前线程被其他线程打断。
public boolean await(long timeout,
TimeUnit unit)
throws InterruptedException
同时await还提供一个带参数和返回值的方法。
如果计数count正常递减,返回0后,await方法会返回true并继续执行后续逻辑。
或是,尚未递减到0,而到达了指定的时间间隔后,方法返回false。
如果时间小于等于0,则此方法不执行等待。
实际案例
join阻塞等待线程完成
首先建立3个线程。
public class Worker1 implements Runnable {
@Override
public void run() {
System.out.println("-线程1启动");
try {
Thread.sleep(13_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1完成--我休眠13秒\r\n");
}
}
public class Worker2 implements Runnable {
@Override
public void run() {
System.out.println("-线程2启动");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2完成--我休眠3秒\r\n");
}
}
public class Worker3 implements Runnable {
@Override
public void run() {
System.out.println("-线程3启动");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3完成--我休眠6秒\r\n");
System.out.println();
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker1 worker1 = new Worker1();
Worker2 worker2 = new Worker2();
Worker3 worker3 = new Worker3();
Thread thread1 = new Thread(worker1,"线程1");
Thread thread2 = new Thread(worker2,"线程2");
Thread thread3 = new Thread(worker3,"线程3");
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("主线程结束....");
}
}
打印结果如下:
-线程3启动
-线程2启动
-线程1启动
线程2完成--我休眠3秒
线程3完成--我休眠6秒
线程1完成--我休眠13秒
主线程结束....
Process finished with exit code 0
可以看出三个线程是并行执行的。启动顺序,并不和执行完毕的顺序一致,但可以明确的是,主线程为一直阻塞,直到三个线程执行完毕。
CountDownLatch用法
阿里巴巴的数据库连接池Druid中也用了countDownLatch来保证初始化。

// 开启创建连接的线程,如果线程池createScheduler为null,
//则开启单个创建连接的线程
createAndStartCreatorThread();
// 开启销毁过期连接的线程
createAndStartDestroyThread();
自己编写一个例子:
这里模拟一种情况:
主线程 依赖 线程A初始化三个数据,才能继续加载后续逻辑。

public class CountDownArticle {
/**
* 模拟 主线程 依赖 线程A初始化一个数据,才能继续加载后续逻辑
*/
public static void main(String[] args) throws InterruptedException {
AtomicReference<String> key = new AtomicReference<>("");
CountDownLatch countDownLatch = new CountDownLatch(3);
Thread t = new Thread(() -> {
try {
//休眠5秒,模拟数据的初始化
TimeUnit.SECONDS.sleep(5);
key.set("核心秘钥123456");
System.out.println("数据1初始化完毕");
//释放---此处可以在任何位置调用,很灵活
countDownLatch.countDown();
System.out.println("数据2初始化完毕");
countDownLatch.countDown();
System.out.println("数据3初始化完毕");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
//等待数据初始化,阻塞
countDownLatch.await();
System.out.println("key:" + key.get());
}
}
打印内容如下:
数据1初始化完毕
数据2初始化完毕
数据3初始化完毕
key:核心秘钥123456
CountDownLatch和Join用法的区别?
在使用join()中,多个线程只有在执行完毕之后欧才能被解除阻塞,而在CountDownLatch中,线程可以在任何时候任何位置调用countdown方法减少计数,通过这种方式,我们可以更好地控制线程的解除阻塞,而不是仅仅依赖于连接线程的完成。
join()方法的执行逻辑如下图所示:

原理
从源码可以看出,CountDownLatch是依赖于AbstractQueuedSynchronizer来实现这一系列逻辑的。
队列同步器AbstractQueuedSynchronizer
是一个用来构建锁和同步器的框架,它在内部定义了一个被标识为volatile的名为state的变量,用来表示同步状态。
多个线程之间可以通过AQS来独占式或共享式的抢占资源。
并且它通过内置的FIFO队列来完成线程的排队工作。

CountDownLatch中的Sync会优先尝试修改state的值,来获取同步状态。例如,如果某个线程成功的将state的值从0修改为1,表示成功的获取了同步状态。 这个修改的过程是通过CAS完成的,所以可以保证线程安全。
反之,如果修改state失败,则会将当前线程加入到AQS的队列中,并阻塞线程。
总结
CountDownLatch(int N) 中的计数器,可以让我们支持最多等待N个线程的操作完成,或是一个线程操作N次。
如果仅仅只需要等待线程的执行完毕,那么join可能就能满足。但是如果需要灵活的控制线程,使用CountDownLatch。
注意事项
countDownLatch.countDown();
这一句话尽量写在finally中,或是保证此行代码前的逻辑正常运行,因为在一些情况下,出现异常会导致无法减一,然后出现死锁。
CountDownLatch 是一次性使用的,当计数值在构造函数中初始化后,就不能再对其设置任何值,当 CountDownLatch 使用完毕,也不能再次被使用。
写在最后
为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
问题时,都可以在这个大家庭里寻求帮助。
公众号回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
如果你也经历过遇到项目难题,无从下手,
他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。
欢迎来公众号【侠梦的开发笔记】,回复干货,领取精选学习视频一份
多线程之CountDownLatch的用法及原理笔记的更多相关文章
- Java并发编程之CountDownLatch的用法
一.含义 CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能.CountDownLatch是一个同步的辅助类,它可以允许一个或多个线程等待, ...
- 多线程之CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- JAVA多线程之CountDownLatch与join的区别
首先,我们来看一个应用场景1: 假设一条流水线上有三个工作者:worker0,worker1,worker2.有一个任务的完成需要他们三者协作完成,worker2可以开始这个任务的前提是worker0 ...
- Java多线程之CountDownLatch学习
给出官网上的例子:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html Java中conc ...
- JAVA多线程之CountDownLatch
前序: 上周测试给开发的同事所开发的模块提出了一个bug,并且还是偶现. 经过仔细查看代码,发现是在业务中启用了多线程,2个线程同时跑,但是新启动的2个线程必须保证一个完成之后另一个再继续运行,才能消 ...
- 多线程之CountDownLatch和CyclicBarriar使用
CountDownLatch和CyclicBarriar是java.util.concurrent包下面提供的多线程同步工具,两者有点相似,相当于计数器,但是用处还是有区别的. CountDownLa ...
- Java多线程之CountDownLatch和CyclicBarrier同步屏障的使用
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6558349.html 一:CountDownLatch CountDownLatch是一个执行 完成任务 ...
- 多线程之CountDownLatch
下面请看一个应用场景:有1个driver和5个worker,需要满足以下两点要求: 当driver完成了全部的工作之后才允许worker们开始工作: 当所有的worker都完成了自己的工作之后,dri ...
- iOS多线程之8.NSOPeration的其他用法
本文主要对NSOPeration的一些重点属性和方法做出介绍,以便大家可以更好的使用NSOPeration. 1.添加依赖 - (void)addDependency:(NSOperation * ...
随机推荐
- ansible批量部署nginx
1.1 将nginx的安装包,和安装脚本copy到客户端,并安装脚本 vim /ansible/roles/nginx/tasks/main.yml - name: copy nginx_tar_gz ...
- $hncpc2019E\ Numbers$ 搜索
正解:搜索 解题报告: 传送门$QAQ$ 考虑直接搜所有长度为1的数的位置?然后其他就能确定了嘛$QwQ$ 设每个数的出现次数为$a_i$,状态数为$\prod_{i=0}^9(a_i+1) \le ...
- Callable,阻塞队列,线程池问题
一.说说Java创建多线程的方法 1. 通过继承Thread类实现run方法 2. 通过实现Runnable接口 3. 通过实现Callable接口 4. 通过线程池获取 二. 可以写一个Call ...
- 全网最详细的Ceph14.2.5集群部署及配置文件详解,快来看看吧! -- <2>
部署Ceph集群 Ceph版本选择 Ceph版本来源介绍 Ceph 社区最新版本是 14,而 Ceph 12 是市面用的最广的稳定版本. 第一个 Ceph 版本是 0.1 ,要回溯到 2008 年 1 ...
- 基于Github Pages + docsify,我花了半天就搭建好了个人博客
目录 前言 一些说明 准备工作 上docsify官网看一看 使用docsify命令生成文档站点 部署到Github上 写在最后 前言 "作为一个真正的码农,不能没有自己的个人博客" ...
- 非常完整的线性DP及记忆化搜索讲义
基础概念 我们之前的课程当中接触了最基础的动态规划. 动态规划最重要的就是找到一个状态和状态转移方程. 除此之外,动态规划问题分析中还有一些重要性质,如:重叠子问题.最优子结构.无后效性等. 最优子结 ...
- Python for Data Analysis 学习心得(一) - numpy介绍
一.简介 Python for Data Analysis这本书的特点是将numpy和pandas这两个工具介绍的很详细,这两个工具是使用Python做数据分析非常重要的一环,numpy主要是做矩阵的 ...
- Dockerfile文件记录(用于后端项目部署)
Dockerfile文件记录(用于后端项目部署) 本教程依据个人理解并经过实际验证为正确,特此记录下来,权当笔记. 注:基于linux操作系统(敏感信息都进行了处理) 此文结合另一篇博客共同构成后端服 ...
- linux搭建简单的web服务器
主要想法是:使用虚拟机的Ubuntu系统搭建http服务器,然后在window的浏览器上测试 1.先测试windows和虚拟机上的ubuntu能否相互ping通 2.下载http.tar.gz并拷贝到 ...
- AcWing 244. 谜一样的牛|树状数组
传送门 题目描述 有n头奶牛,已知它们的身高为 1~n 且各不相同,但不知道每头奶牛的具体身高. 现在这n头奶牛站成一列,已知第i头牛前面有Ai头牛比它低,求每头奶牛的身高. 输入格式 第1行:输入整 ...