提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
CountDownLatch的理解及业务使用
一、CountDownLatch相关基础解释
CountDownLatch是Java并发包中的一个工具类,它可以用来控制一个或多个线程等待其他线程完成操作后再继续执行。它的原理是通过一个计数器来实现的,计数器的初始值可以设置为任意整数,当一个线程完成了自己的任务后,计数器的值就会减1,当计数器的值变为0时,所有等待的线程就会被唤醒继续执行。
创建方式:
//锁计数器
int threadCount = 1;
//创建方式
CountDownLatch countDownLatchNow = new CountDownLatch(threadCount);
返回值 | 方法名,解释 |
---|---|
void | await(), 直到当前线程的锁计数器的值为0 ,才执行后续代码,如果线程被中断,否则会一直被阻塞 |
boolean | await(long timout,TimeUnit unit),设置代码阻塞的超时时间,线程被中断或者超时,就会执行后代码 |
void | countDown():,主要减少锁计数器的次数,如果次数是 0 ,就会释放所有正在等待的线程(当然如果锁计数器是 0 ,再调用countDown()这个方法也不会报错,因为他不会产生任何效果) |
long | getCount(), 返回当前countDownLatchNow 中 锁计数器的次数 |
二、CountDownLatch 相关使用
1.最常用的使用场景: 项目中远程调用某个api,我需要解析响应的JSON用于后续的操作。但是这个调用api方法使用三方使用的sdk,且sdk里面的获取结果方法是异步的,怎么让主线程获取到响应的JSON值?
public class CountDownLatchDemo {
public static void main(String[] args) {
ExecutorService executorService = null;
try {
executorService = Executors.newSingleThreadExecutor();
//收集异步线程返回的结果
final StringBuilder resultJson = new StringBuilder();
//模拟远程调用三方api得到结果 resultJson
executorService.submit(() -> {
System.out.println("线程池中执行的线程名称:" + Thread.currentThread().getName());
resultJson.append("三方返回的结果json");
});
//打印下最终返回的结果信息
System.out.println("获取返回结果resultJson的线程名称:" + Thread.currentThread().getName());
//模拟三方接口返回变量的值 resultJson
System.out.println("resultJson结果:" + resultJson.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
// ------ 打印的结果:
//获取返回结果resultJson的线程名称:main
//resultJson结果: <----(这一步并未获取到三方接口的结果)
//线程池中执行的线程名称:pool-1-thread-1
//从结果打印到方式来看,很显然不是我们想要的结果,这个结果也在我们意料之中
使用CountDownLatch 中的countDown() 和 await() 方法 控制线程的执行顺序
public class CountDownLatchDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
CountDownLatch latch = new CountDownLatch(1); //添加 CountDownLatch 控制线程执行
try {
//收集异步线程返回的结果
final StringBuilder resultJson = new StringBuilder();
//模拟远程调用三方api得到结果 resultJson
executorService.submit(() -> {
System.out.println("线程池中执行的线程名称:" + Thread.currentThread().getName());
resultJson.append("三方返回的结果json");
latch.countDown(); //线程内的方法执行完 将计数器 减 1
});
//打印下最终返回的结果信息
latch.await(); // 阻塞主线程,等计数器的值变成 0
System.out.println("获取返回结果resultJson的线程名称:" + Thread.currentThread().getName());
//模拟三方接口返回的值 resultJson
System.out.println("resultJson结果:" + resultJson.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown(); // 释放计数器,防止主线程被阻塞
executorService.shutdown(); //释放资源
}
}
}
//线程池中执行的线程名称:pool-1-thread-1
//获取返回结果resultJson的线程名称:main
//resultJson结果:三方返回的结果json <------ 打印的结果 可以获取到接口返回的结果
2.在以上的场景中还有一种情况, 如远程调用接口大概需要5~10s才能返回结果,但是接口返回的值又不是必然需要的,获取不到可以再次调用接口取,像自己最近将chatgpt 对接到自己微信公众号做消息回复,发现用户提问的问题复杂,chatgpt 接口会响应慢,可能会超过超过微信的回调接口的响应时间,导致chatgpt回复的结果不能很好的从我的服务端输出到微信公众号页面展现给用户.
使用await(long timout,TimeUnit unit) 方法优化原有代码结构
//收集异步线程返回的结果
final StringBuilder resultJson = new StringBuilder();
//模拟远程调用三方api得到结果 resultJson
executorService.submit(() -> {
System.out.println("线程池中执行的线程名称:" + Thread.currentThread().getName());
try {
//模拟接口调用超过 10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resultJson.append("三方返回的结果json");
latch.countDown(); //线程内的方法执行完 将计数器 减 1
});
//打印下最终返回的结果信息
latch.await(); // 这里代码没有优化前,主线程会被一致 阻塞到 10s后才能,或者连接超时被中断,主线程才能被释放,这个接口的吞吐量会相应的降低
// 在这里优化,设置超时时间, 1s 后会自动 让主线程执行后面的代码
// latch.await(1,TimeUnit.SECONDS);
System.out.println("获取返回结果resultJson的线程名称:" + Thread.currentThread().getName());
System.out.println("resultJson结果:" + resultJson.toString());
对于这类情况 :调用三方接口拿到的值可以根据实际场景的需要,将结果存在redis 或者 db 中,便于下次再取的时候,先从缓存来查后者db中查询,输出给用户等等
CountDownLatch总结及使用建议
1.确保计数器的值正确设置:在使用CountDownLatch时,需要确保计数器的初始值是正确的,即与实际需要等待的线程数量相匹配。否则,可能会导致线程一直等待或者无法正常释放。(这一点很重要)
2.使用await()方法等待计数器归零:在主线程或者其他线程中,可以使用await()方法来等待计数器归零。该方法会阻塞当前线程,直到计数器的值为0,或者等待超时。
3.使用countDown()方法递减计数器:在需要等待的线程中,通过调用countDown()方法来递减计数器的值。一般建议在任务完成后立即调用countDown()方法,以确保计数器能够正确递减。
4.注意线程安全:CountDownLatch是线程安全的,可以在多线程环境下使用。但需要注意,不要在多个线程中共享同一个CountDownLatch实例,以免出现竞争条件。
5.在使用await()方法时,可以设置等待超时时间。其实大部分的业务应该都需要设置超时时间,并且能够处理超时情况。(这里很重要,相当于一种兜底,可以理解为,你的while 循环代码中, 有一种条件必走 break 的代码)
6.可以使用多个CountDownLatch:在复杂的场景中,可以使用多个CountDownLatch来实现更复杂的等待和同步逻辑。