倒计时CountDownLatch
CountDownLatch是在java1.5被引入的,它存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
CountDownLatch.java类中定义的构造函数:public CountDownLatch(int count) { ... }
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
CountDownLatch在实时系统中的使用场景
让我们尝试罗列出在java实时系统中CountDownLatch都有哪些使用场景。我所罗列的都是我所能想到的。如果你有别的可能的使用方法,请在留言里列出来,这样会帮助到大家。
(1)实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
(2)开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
(3)死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
CountDownLatch是一个非常实用的多线程控制工具类,称之为“倒计时器”,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
举了例子:
在这个例子中,我模拟了一个应用程序启动类,它开始时启动了n个线程类,这些线程将检查外部系统并通知闭锁,并且启动类一直在闭锁上等待着。一旦验证和检查了所有外部服务,那么启动类恢复执行。
BaseHealthChecker.java:这个类是一个Runnable,负责所有特定的外部服务健康的检测。
package com.threadcounter;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.CountDownLatch;
/**
* @author wanjiadong
* @description
* @date Create in 11:53 2019/1/7
*/
@Getter
@Setter
public abstract class BaseHealthChecker implements Runnable {
private CountDownLatch countDownLatch;
private String serviceName;
private boolean serviceIsUp;
//Get latch object in constructor so that after completing the task, thread can countDown() the latch
public BaseHealthChecker(CountDownLatch downLatch, String serviceName) {
super();
this.countDownLatch = downLatch;
this.serviceName = serviceName;
this.serviceIsUp = false;
}
@Override
public void run() {
try {
verifyService();
serviceIsUp = true;
} catch (Exception e) {
serviceIsUp = false;
e.printStackTrace();
} finally {
if(null != countDownLatch) {
countDownLatch.countDown();
}
}
}
//This methos needs to be implemented by all specific service checker
public abstract void verifyService() throws Exception;
}
NetworkHealthChecker.java:这个类继承了BaseHealthChecker,实现了verifyService()方法。
package com.threadcounter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* @author wanjiadong
* @description
* @date Create in 13:46 2019/1/7
*/
@Slf4j
public class CacheHealthCheckter extends BaseHealthChecker {
public CacheHealthCheckter(CountDownLatch downLatch) {
super(downLatch, "CacheHealthCheckter");
}
@Override
public void verifyService() throws Exception {
log.info("Checking : " + this.getServiceName());
try {
Thread.sleep(3000L);
throw new Exception();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
throw new Exception();
}
log.info(this.getServiceName() + " is UP!");
}
}
package com.threadcounter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* @author wanjiadong
* @description
* @date Create in 13:44 2019/1/7
*/
@Slf4j
public class DataBaseHealthCheckter extends BaseHealthChecker {
public DataBaseHealthCheckter(CountDownLatch downLatch) {
super(downLatch, "DataBaseHealthCheckter");
}
@Override
public void verifyService() {
log.info("Checking : " + this.getServiceName());
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(this.getServiceName() + " is UP!");
}
}
package com.threadcounter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* @author wanjiadong
* @description
* @date Create in 12:00 2019/1/7
*/
@Slf4j
public class NetWorkHealthChecker extends BaseHealthChecker {
public NetWorkHealthChecker(CountDownLatch downLatch) {
super(downLatch, "netWorkHealthChecker");
}
@Override
public void verifyService() {
log.info("Checking : " + this.getServiceName());
try {
Thread.sleep(7000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(this.getServiceName() + " is UP!");
}
}
ApplicationStartupUtil.java:这个类是一个主启动类,它负责初始化闭锁,然后等待,直到所有服务都被检测完。
package com.threadcounter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @author wanjiadong
* @description
* @date Create in 13:49 2019/1/7
*/
public class ApplicationStartUpUtil {
//This latch will be used to wait on
private static CountDownLatch countDownLatch;
private static List<BaseHealthChecker> serivces;
private ApplicationStartUpUtil instance = new ApplicationStartUpUtil();
private ApplicationStartUpUtil() {}
public ApplicationStartUpUtil getInstance() {
return instance;
}
public static boolean checkServices() throws InterruptedException {
//Initialize the latch with number of service checkers
countDownLatch = new CountDownLatch(3);
//All add checker in lists
serivces = new ArrayList<>();
serivces.add(new CacheHealthCheckter(countDownLatch));
serivces.add(new DataBaseHealthCheckter(countDownLatch));
serivces.add(new NetWorkHealthChecker(countDownLatch));
//Start service checkers using executor framework
Executor executor = Executors.newFixedThreadPool(serivces.size());
serivces.forEach(baseHealthChecker -> {
executor.execute(baseHealthChecker);
});
//Now wait till all services are checked
countDownLatch.await();
//Services are file and now proceed startup
for(BaseHealthChecker checker : serivces) {
if(!checker.isServiceIsUp()) {
return false;
}
}
return true;
}
}
测试:
package com.threadcounter;
import lombok.extern.slf4j.Slf4j;
/**
* @author wanjiadong
* @description
* @date Create in 14:00 2019/1/7
*/
@Slf4j
public class TestStart {
public static void main(String[] args) {
boolean result = false;
try {
result = ApplicationStartUpUtil.checkServices();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("External services validation completed !! Result was :: "+ result);
}
}
执行结果:
常见面试题
可以为你的下次面试准备以下一些CountDownLatch相关的问题:
- 解释一下CountDownLatch概念?
- CountDownLatch 和CyclicBarrier的不同之处?
- 给出一些CountDownLatch使用的例子?
- CountDownLatch 类中主要的方法?
循环屏障CyclicBarrier
CyclicBarrier是另一种多线程并发控制使用工具,和CountDownLatch非常类似,他也可以实现线程间的计数等待,但他的功能要比CountDownLatch更加强大一些。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
例子:“集齐七颗龙珠!召唤神龙”的故事
package com.threadcounter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author wanjiadong
* @description
* @date Create in 14:21 2019/1/7
*/
@Slf4j
public class SummonDragonDemo {
private static int num = 7;
public static void main(String[] args) {
//设置第一个屏障点,等待召集齐7位法师
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, new Runnable() {
@Override
public void run() {
log.info("7个龙珠法师集合完毕,前往各个地方收集龙珠!");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
summonDragon();
}
});
for(int i=1; i<=num; i++) {
int finalI = i;
new Thread(() -> {
log.info("第"+ finalI +"个法师召集完毕!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
private static void summonDragon() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, new Runnable() {
@Override
public void run() {
log.info("7颗龙珠收集完毕");
}
});
for(int i=1; i<=num; i++) {
int finalI = i;
new Thread(() -> {
log.info("第"+ finalI +"个龙珠收集完毕!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
执行结果:
CyclicBarrier和CountDownLatch的区别
(1)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
(2)CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。
(3)CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。