1、CountDownLatch的用法
其核心机制是通过一个计数器实现线程等待,计数归零的时候,释放所有线程。
创建计数器:创建时指定正整数(如 N),表示需要等待的线程数量或任务数。
子线程完成任务后调用 countDown(),计数器减 1。
主线程调用 await() 阻塞,直到计数器归零后才会继续执行主线程代码。
2、CountDownLatch 和 Future的get方法区别
Future的get方法也可以实现线程等待,但它是针对单个任务(线程),且get方法是顺序阻塞的。比如,有多个任务,调用get方法会顺序获取,就算第二个线程早就执行完了,也会等待第一个Future的get方法拿到返回之后,才会执行第二个Future的get方法。
而CountDownLatch是针对一组任务,他不关心具体线程,只重视一组任务(线程)的结果。
3、场景
假设,预订三间房间。从 【预订】 --> 【预定成功/失败】,每一间房肯定有不少的校验。最后,全部预订成功了,才会发送邮件,有一间房预订失败了,就发送具体失败哪一间房。
这个时候,就可以使用 CountDownLatch ,将【预订】到【预定成功/失败】的这个流程单独弄成异步的,然后主流程等待异步线程全部结束,在执行发邮件的流程。
4、代码实现
返回结果定义
java
public class BookRoomResult {
private String success;
private String roomId; //房间号(唯一键)
private String errorMsg;
public BookRoomResult(String success, String roomId, String errorMsg) {
this.success = success;
this.roomId = roomId;
this.errorMsg = errorMsg;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
多线程模拟
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo1 {
//核心线程数,核心线程默认会一直存在
public static int corePoolSize = 5;
//最大线程数
public static int maximumPoolSize = 15;
//非核心线程的线程存活时间
public static int keepAliveTime = 2;
public static void main(String[] args) {
ThreadPoolExecutor executor = buildThreadPool();
List<String> room = Arrays.asList("room1", "room2", "room3");
//有多少间房,就创建多大的计数器
CountDownLatch latch = new CountDownLatch(room.size());
List<Future<BookRoomResult>> result = new ArrayList<>();
for (int i = 0; i < room.size(); i++) {
try {
int finalI = i;
Future<BookRoomResult> submit = executor.submit(new Callable() {
@Override
public BookRoomResult call() {
try {
if (finalI == 2) {
Thread.sleep(2000);
return new BookRoomResult("fail", room.get(finalI), "预订失败,客房满了");
}
} catch (Exception e) {
System.out.println("异步处理发生未知异常");
} finally {
//每个线程内的任务执行完毕后,计数器减 1
latch.countDown();
}
return new BookRoomResult("success", room.get(finalI), "预订成功");
}
});
result.add(submit);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
//主线程阻塞10s,等待计数器归零
//10秒内如果异步线程全部执行完毕了,那就走正常流程
if (latch.await(10, TimeUnit.SECONDS)) {
//到这一步了,说明每个线程的任务执行完毕,用get方法肯定是不会出现阻塞
for (Future<BookRoomResult> bookRoom : result) {
if ("fail".contains(bookRoom.get().getSuccess())) {
System.out.println("存在失败房间:" + bookRoom.get().getRoomId() + "失败原因:" + bookRoom.get().getErrorMsg());
}
}
} else {
System.out.println("10s内没有收到全部的异步线程返回,疑似网络出问题");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("==================主线程执行完毕===================");
}
/**
* 定义一个线程池
*
* @return
*/
public static ThreadPoolExecutor buildThreadPool() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10), //有界队列,容量10
Executors.defaultThreadFactory(),
(r, executor1) -> System.out.println("线程超过最大线程数"));
return executor;
}
}