深入浅出Java并发中的CountDownLatch
1. CountDownLatch
正如每个Java文档所描述的那样,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。在这篇文章中,我将会涉及到在Java并发编 程中跟CountDownLatch相关的以下几点:
- CountDownLatch是什么?
- CountDownLatch如何工作?
- 在实时系统中的应用场景
- 应用范例
- 常见的面试题
1.1 CountDownLatch是什么
CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
CountDownLatch的伪代码如下所示:
//Main thread start
//Create CountDownLatch for N threads
//Create and start N threads
//Main thread wait on latch (待执行的程序先执行,这里通过CountDownLatch的值来判断前面的线程是否执行完毕,如果没有执行完毕会一直卡着)
//N threads completes there tasks are returns
//Main thread resume execution
ps:注意这里的顺序
1.2 CountDownLatch如何工作
CountDownLatch.java类中定义的构造函数:
//Constructs a CountDownLatch initialized with the given count.
public void CountDownLatch(int count) {...}
执行过程:
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。
这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
与CountDownLatch的第一次交互是主线程等待其他线程。
主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。(重要)
其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。
这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。
所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
1.3 在实时系统中的使用场景
让我们尝试罗列出在java实时系统中CountDownLatch都有哪些使用场景。我所罗列的都是我所能想到的。如果你有别的可能的使用方法,请在留言里列出来,这样会帮助到大家。
- 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
- 开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
- 死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
1.4 CountDownLatch使用例子
场景:在这个例子中,我模拟了一个应用程序启动类,它开始时启动了n个线程类,这些线程将检查外部系统并通知闭锁,并且启动类一直在闭锁上等待着。一旦验证和检查了所有外部服务,那么启动类恢复执行。
BaseHealthChecker.java:这个类是一个Runnable,负责所有特定的外部服务健康的检测。它删除了重复的代码和闭锁的中心控制代码。
package countDown; import java.util.concurrent.CountDownLatch; public abstract class BaseHealthChecker implements Runnable { private CountDownLatch _latch;
private String _serviceName;
private boolean _serviceUp; //用来检测线程任务是否都成功执行完毕 //Get latch object in constructor so that after completing the task, thread can countDown() the latch
public BaseHealthChecker(String serviceName, CountDownLatch latch)
{
super();
this._latch = latch;
this._serviceName = serviceName;
this._serviceUp = false;
} @Override
public void run() {
try {
verifyService();
_serviceUp = true;
} catch (Throwable t) {
t.printStackTrace(System.err);
_serviceUp = false;
} finally { //不管程序有无异常,都会执行一次countDown()
if(_latch != null) {
_latch.countDown();
}
}
} public String getServiceName() {
return _serviceName;
} public boolean isServiceUp() {
return _serviceUp;
}
//This methos needs to be implemented by all specific service checker
public abstract void verifyService();
}
NetworkHealthChecker.java:这个类继承了BaseHealthChecker,实现了verifyService()方法。DatabaseHealthChecker.java和CacheHealthChecker.java除了服务名和休眠时间外,与NetworkHealthChecker.java是一样的。
package countDown; import java.util.concurrent.CountDownLatch; public class NetworkHealthChecker extends BaseHealthChecker{
public NetworkHealthChecker (CountDownLatch latch) {
super("Network Service", latch);
} @Override
public void verifyService()
{
System.out.println("Checking " + this.getServiceName());
try
{
Thread.sleep(7000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(this.getServiceName() + " is UP");
}
}
package countDown; import java.util.concurrent.CountDownLatch; public class DatabaseHealthChecker extends BaseHealthChecker { public DatabaseHealthChecker (CountDownLatch latch) {
super("Database Service", latch);
} @Override
public void verifyService()
{
System.out.println("Checking " + this.getServiceName());
try
{
Thread.sleep(7000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(this.getServiceName() + " is UP");
}
}
package countDown; import java.util.concurrent.CountDownLatch; public class CacheHealthChecker extends BaseHealthChecker { public CacheHealthChecker (CountDownLatch latch) {
super("Cache Service", latch);
} @Override
public void verifyService()
{
System.out.println("Checking " + this.getServiceName());
try
{
Thread.sleep(7000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(this.getServiceName() + " is UP");
}
}
ApplicationStartupUtil.java:这个类是一个主启动类,它负责初始化闭锁,然后等待,直到所有服务都被检测完。
package countDown; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
*
* 启动类通过CountDownLatch来等待其它线程执行完毕
*
*/
public class ApplicationStartupUtil
{
//List of service checkers
private static List<BaseHealthChecker> _services; //This latch will be used to wait on
private static CountDownLatch _latch; private ApplicationStartupUtil()
{
} //单例启动类
private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil(); public static ApplicationStartupUtil getInstance()
{
return INSTANCE;
} public static boolean checkExternalServices() throws Exception
{
//Initialize the latch with number of service checkers
_latch = new CountDownLatch(3); //All add checker in lists
_services = new ArrayList<BaseHealthChecker>();
_services.add(new NetworkHealthChecker(_latch));
_services.add(new CacheHealthChecker(_latch));
_services.add(new DatabaseHealthChecker(_latch)); //Start service checkers using executor framework
Executor executor = Executors.newFixedThreadPool(_services.size()); for(final BaseHealthChecker v : _services)
{
executor.execute(v);
} //Now wait till all services are checked
_latch.await(); //Services are file and now proceed startup
//任何一个service没有正确结束,mian最后都不能返回正确的结果
for(final BaseHealthChecker v : _services)
{
if( ! v.isServiceUp())
{
return false;
}
}
return true;
} }
现在你可以写测试代码去检测一下闭锁的功能了。
package countDown; public class Main {
public static void main(String[] args){
boolean result = false; //检测所有结果是否正确的执行
try {
result = ApplicationStartupUtil.checkExternalServices();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("External services validation completed !! Result was :: "+ result);
}
}
1.5 常见面试题
可以为你的下次面试准备以下一些CountDownLatch相关的问题:
- 解释一下CountDownLatch概念?
- CountDownLatch 和CyclicBarrier的不同之处?
- 给出一些CountDownLatch使用的例子?
- CountDownLatch 类中主要的方法?
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
这里展示两个api中的示例,通俗易懂:
示例用法: 下面给出了两个类,其中一组 worker 线程使用了两个倒计数锁存器:
- 第一个类是一个启动信号,在 driver 为继续执行 worker 做好准备之前,它会阻止所有的 worker 继续执行。
- 第二个类是一个完成信号,它允许 driver 在完成所有 worker 之前一直等待。
class Driver { // ...
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don't let run yet
startSignal.countDown(); // let all threads proceed 此时才开始执行worker线程
doSomethingElse();
doneSignal.await(); // wait for all to finish 等待所有worker执行完之后才继续执行
}
} class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
} void doWork() { ... }
}另一种典型用法是,将一个问题分成 N 个部分,用执行每个部分并让锁存器倒计数的 Runnable 来描述每个部分,然后将所有 Runnable 加入到 Executor 队列。当所有的子部分完成后,协调线程就能够通过 await。(当线程必须用这种方法反复倒计数时,可改为使用
CyclicBarrier
。)class Driver2 { // ...
void main() throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
Executor e = ... for (int i = 0; i < N; ++i) // create and start threads
e.execute(new WorkerRunnable(doneSignal, i)); doneSignal.await(); // wait for all to finish 等待N个worker线程执行完之后,才开始执行Driver2
}
} class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
try {
doWork(i);
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
} void doWork() { ... }
}
2. 使用 CountDownLatch 控制多个线程执行顺序
(转自另外一篇博客,通俗易懂)
有时候会有这样的需求,多个线程同时工作,然后其中几个可以随意并发执行,但有一个线程需要等其他线程工作结束后,才能开始。
举个例子,开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段,那么这时候我们可以考虑使用CountDownLatch来控制并发。
ps:工作过程
CountDownLatch是JAVA提供在java.util.concurrent包下的一个辅助类,
可以把它看成是一个计数器,其内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器,
CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的cutDown()方法,来使计数减1;
如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0,才可以继续执行。
/**
*
*/ package work1128.singleton; import java.util.concurrent.CountDownLatch; /**
* ClassName: Sample <br/>
* Function: 演示CountDownLatch的执行过程
*/
public class Sample {
/**
* 计数器,用来控制线程
* 传入参数2,表示计数器计数为2
*/
private final static CountDownLatch mCountDownLatch = new CountDownLatch(2); /**
* 示例工作线程类
*/
private static class WorkingThread extends Thread {
private final String mThreadName; //线程名称
private final int mSleepTime; //睡眠时间
public WorkingThread(String name, int sleepTime) {
mThreadName = name;
mSleepTime = sleepTime;
} @Override
public void run() {
System.out.println("[" + mThreadName + "] started!");
try {
Thread.sleep(mSleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
mCountDownLatch.countDown();
System.out.println("[" + mThreadName + "] end!");
}
} /**
* 示例线程类
*/
private static class SampleThread extends Thread { @Override
public void run() {
System.out.println("[SampleThread] started!");
try {
// 会阻塞在这里等待 mCountDownLatch 里的count变为0;
// 也就是等待另外的WorkingThread调用countDown()
mCountDownLatch.await();
} catch (InterruptedException e) { }
System.out.println("[SampleThread] end!");
}
} public static void main(String[] args) throws Exception {
// 最先run SampleThread ,后面线程执行完毕后,才会回来执行SampleThread这个线程
new SampleThread().start();
// 运行两个工作线程
// 工作线程1运行5秒
new WorkingThread("WorkingThread1", 5000).start();
// 工作线程2运行2秒
new WorkingThread("WorkingThread2", 2000).start();
}
}
运行结果:
[SampleThread] started!
[WorkingThread1] started!
[WorkingThread2] started!
[WorkingThread2] end!
[WorkingThread1] end!
[SampleThread] end!
达到了目的。当然还有其他方式可以做到这样的效果,本文仅仅是介绍了一种使用CountDownLatch的方式。
深入浅出Java并发中的CountDownLatch的更多相关文章
- Java并发工具类CountDownLatch源码中的例子
Java并发工具类CountDownLatch源码中的例子 实例一 原文描述 /** * <p><b>Sample usage:</b> Here is a pai ...
- java并发中CountDownLatch的使用
文章目录 主线程等待子线程全都结束之后再开始运行 等待所有线程都准备好再一起执行 停止CountdownLatch的await java并发中CountDownLatch的使用 在java并发中,控制 ...
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- Java并发工具类 - CountDownLatch
Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http: ...
- 14、Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- 【转】Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在j ...
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore (总结)
下面对上面说的三个辅助类进行一个总结: 1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDownLatch一般用于某个线程A等待 ...
- java并发中ExecutorService的使用
文章目录 创建ExecutorService 为ExecutorService分配Tasks 关闭ExecutorService Future ScheduledExecutorService Exe ...
- java并发中的Synchronized关键词
文章目录 为什么要同步 Synchronized关键词 Synchronized Instance Methods Synchronized Static Methods Synchronized B ...
随机推荐
- php表单的种类
1.text 2.color 3.date 4.datetime 5.email 6.month 7.number 8.range 9.search 10.tel 11.url 12.week 13. ...
- 2018-8-10-win10-uwp-如何创建修改保存位图
title author date CreateTime categories win10 uwp 如何创建修改保存位图 lindexi 2018-08-10 19:16:50 +0800 2018- ...
- [ITOO]动态建库 标签: 库数据库mysql 2016-07-17 21:23 241人阅读 评论(2) 收
最近一直在做权限系统的动态建库,动态建库,说白了就是在你点击"注册"按钮的时候,根据你输入的信息,来创建一个企业所需要的数据库的过程,因为现阶段并没有提供购买等功能,所以暂时咱们是 ...
- 运行docker容器镜像
docker容器可以理解为在盒中运行的进程. 这个盒包含了该进程运行所必须的资源,包括文件系统.系统类库.shell 环境等等. 但这个盒默认是不会运行任何程序的. 1.运行镜像之前,可以先查看本地有 ...
- aspcms安装所遇到的问题
aspcms标签:http://biaoqian.iasp.com.cn/ 1.报错:An error occurred on the server when processing the URL. ...
- Xcode无法退出,报错提示 The document “xxx.h” could not be saved. The file doesn’t exist.
记录一个问题 场景:Xcode编辑一个工程时直接在工程内部修改了某个目录的文件夹名字,而后删除了其下的某 .h.m 文件 之后总是提示上述错误且无法强制退出Xcode,clean等操作基本没用 查找本 ...
- @noi.ac - 506@ 强连通分量
目录 @description@ @solution@ @accepted code@ @details@ @description@ 有一天你学了一个叫能求出有向图中所有的强连通分量的算法,你决定将 ...
- poj 3334 Connected Gheeves (Geometry + BInary Search)
3334 -- Connected Gheeves 题意是,给出两个尖形的相连的容器,要求向其中灌水.它们具有日常的物理属性,例如两个容器中水平面高度相同以及水高于容器顶部的时候就会溢出.开始的时候打 ...
- ITA 15.5 Optimal binary search trees
p400 页最后一段 When j >= i , we need to select a root kr from among ki ... kj and then make an optima ...
- Python--day30--网络基础
单波: 查找mac: