李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
57.CountdownLatch介绍
Leefs
2022-12-01 PM
489℃
0条
[TOC] ### 一、概述 `CountDownLatch(倒计时锁)` 能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经任务了,然后在 `CountDownLatch` 上等待的线程就可以恢复执行接下来的任务。 #### 应用场景 典型的应用场景就是当一个服务启动时,同时会加载很多组件和服务,这时候主线程会等待组件和服务的加载。当所有的组件和服务都加载完毕后,主线程和其他线程在一起完成某个任务。 ### 二、常用方法 | 方法 | 说明 | | --------------------------- | ------------------------------------------------------------ | | `CountDownLatch(int count)` | count为计数器的初始值(一般需要多少个线程执行,count就设为几) | | `countDown()` | 每调用一次计数器值-1,直到count被减为0,代表所有线程全部执行完毕 | | `getCount()` | 获取当前计数器的值 | | `await()` | 等待计数器变为0,即等待所有异步线程执行完毕 | + `boolean await(long timeout, TimeUnit unit)`: 此方法与await()区别 + 此方法至多会等待指定的时间,超时后会自动唤醒,若 timeout 小于等于零,则不会等待; + boolean 类型返回值:若计数器变为零了,则返回 true;若指定的等待时间过去了,则返回 false。 **CountDownLatch与join的区别?** join相对而言属于比较底层的API,使用比较繁琐,且**必须等待线程结束**,`CountDownLatch`提供了更加灵活的与线程同步的方式(线程可以在完成某个条件调用countDown()方法)。 ### 三、CountDownLatch使用 #### 3.1 简单应用 > 需求:开启三个线程,分别睡眠不同的时间,当等待三个线程都执行完成后,最后主线程退出 ```java import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import static com.lilinchao.concurrent.utils.Sleeper.sleep; /** * Created by lilinchao * Date 2022/12/1 * Description CountdownLatch示例 */ @Slf4j(topic = "c.CountdownLatchDemo") public class CountdownLatchDemo { public static void main(String[] args) throws InterruptedException { // 初始倒计时为3 CountDownLatch latch = new CountDownLatch(3); new Thread(() -> { log.debug("begin..."); sleep(1); latch.countDown(); // 计数减一 log.debug("end...{}", latch.getCount()); }).start(); new Thread(() -> { log.debug("begin..."); sleep(2); latch.countDown(); log.debug("end...{}", latch.getCount()); }).start(); new Thread(() -> { log.debug("begin..."); sleep(1.5); latch.countDown(); log.debug("end...{}", latch.getCount()); }).start(); log.debug("waiting..."); // 等待计数归零 latch.await(); log.debug("wait end..."); } } ``` **运行结果** ``` 22:07:45.820 [main] DEBUG c.CountdownLatchDemo - waiting... 22:07:45.820 [Thread-0] DEBUG c.CountdownLatchDemo - begin... 22:07:45.820 [Thread-1] DEBUG c.CountdownLatchDemo - begin... 22:07:45.820 [Thread-2] DEBUG c.CountdownLatchDemo - begin... 22:07:46.825 [Thread-0] DEBUG c.CountdownLatchDemo - end...2 22:07:47.325 [Thread-2] DEBUG c.CountdownLatchDemo - end...1 22:07:47.824 [Thread-1] DEBUG c.CountdownLatchDemo - end...0 22:07:47.824 [main] DEBUG c.CountdownLatchDemo - wait end... ``` 从结果可以看出,主线程等待其他三个线程执行完毕才往下执行。 #### 3.2 CountDownLatch配合线程池使用 > 需求:同时开启四个线程,三个线程负责处理,一个线程负责汇总结果 ```java import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.lilinchao.concurrent.utils.Sleeper.sleep; /** * Created by lilinchao * Date 2022/12/1 * Description 配合线程池使用 */ @Slf4j(topic = "c.CountdownLatchDemo02") public class CountdownLatchDemo02 { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(3); ExecutorService service = Executors.newFixedThreadPool(4); service.submit(() -> { log.debug("begin..."); sleep(1); latch.countDown(); log.debug("end...{}", latch.getCount()); }); service.submit(() -> { log.debug("begin..."); sleep(1.5); latch.countDown(); log.debug("end...{}", latch.getCount()); }); service.submit(() -> { log.debug("begin..."); sleep(2); latch.countDown(); log.debug("end...{}", latch.getCount()); }); service.submit(()->{ try { log.debug("waiting..."); latch.await(); log.debug("wait end..."); } catch (InterruptedException e) { e.printStackTrace(); } }); } } ``` **运行结果** ``` 22:11:58.111 [pool-1-thread-1] DEBUG c.CountdownLatchDemo02 - begin... 22:11:58.111 [pool-1-thread-4] DEBUG c.CountdownLatchDemo02 - waiting... 22:11:58.111 [pool-1-thread-2] DEBUG c.CountdownLatchDemo02 - begin... 22:11:58.111 [pool-1-thread-3] DEBUG c.CountdownLatchDemo02 - begin... 22:11:59.160 [pool-1-thread-1] DEBUG c.CountdownLatchDemo02 - end...2 22:11:59.616 [pool-1-thread-2] DEBUG c.CountdownLatchDemo02 - end...1 22:12:00.115 [pool-1-thread-3] DEBUG c.CountdownLatchDemo02 - end...0 22:12:00.115 [pool-1-thread-4] DEBUG c.CountdownLatchDemo02 - wait end... ``` 从结果可以看出,负责汇总的线程执行到`latch.await();`进入到等待状态,等其他三个线程全部执行结束之后,再向下执行。 ### 四、CountDownLatch典型应用场景介绍 #### 场景1:多个游戏资源并发加载完成通知 > 在这个场景下,某个线程进入到下一个流程,需要多个资源加载好,这里假定为10个,可以采用线程池,令该线程提交10个任务分别加载一个资源,然后调用`countDownLatch.await`去等待所有资源加载完毕,每个任务准备好资源就`countDown`。 **示例代码** ```java import java.util.Arrays; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; /** * Created by lilinchao * Date 2022/12/1 * Description 同步等待多个线程准备完毕 */ public class CountdownLatchDemo03 { public static void main(String[] args) throws InterruptedException { AtomicInteger num = new AtomicInteger(0); ExecutorService service = Executors.newFixedThreadPool(10, (r) -> { return new Thread(r, "t" + num.incrementAndGet()); }); String[] all = new String[10]; CountDownLatch latch = new CountDownLatch(10); Random random = new Random(); // 模拟提交10个线程任务 for (int i = 0; i < 10; i++) { int x = i; // 线程池中每个任务模拟加载资源,当资源加载完毕,调用countDown(),将state-- service.submit(() -> { for (int j = 0; j <= 100; j++) { try { // 随机睡眠时间,使得每个线程加载完成时间不同 Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } all[x] = Thread.currentThread().getName() + "(" + j + "%" + ")"; System.out.print("\r" + Arrays.toString(all)); } latch.countDown(); }); } latch.await(); System.out.print("\n游戏开始"); service.shutdown(); } } ``` **运行结果** ``` [t1(100%), t2(100%), t3(100%), t4(100%), t5(100%), t6(100%), t7(100%), t8(100%), t9(100%), t10(100%)] 游戏开始 ``` 当所有资源都加载好了,才进行下一个流程,游戏开始 #### **场景2**微服务场景下多个RPC远程调用并发执行 > 在这个场景下,当前用户的请求,需要多个服务器资源,比如需要商品信息,订单信息,快递信息共三个信息,则需要进行三次远程调用,如果顺序执行的话,显然效率比较低下。 > 此时可以采用多线程的方式利用线程池配合`CountDownLatch`并发的去获取资源(采用线程的future对象返回线程执行完返回的结果),节约时间。 **示例代码** ```java @RestController public class TestCountDownlatchController { @GetMapping("/order/{id}") public Map
order(@PathVariable int id) { HashMap
map = new HashMap<>(); map.put("id", id); map.put("total", "2300.00"); sleep(2000); return map; } @GetMapping("/product/{id}") public Map
product(@PathVariable int id) { HashMap
map = new HashMap<>(); if (id == 1) { map.put("name", "小爱音箱"); map.put("price", 300); } else if (id == 2) { map.put("name", "小米手机"); map.put("price", 2000); } map.put("id", id); sleep(1000); return map; } @GetMapping("/logistics/{id}") public Map
logistics(@PathVariable int id) { HashMap
map = new HashMap<>(); map.put("id", id); map.put("name", "中通快递"); sleep(2500); return map; } private void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` **rest 远程调用** + **CountDownLatch 实现** ```java /** * Created by lilinchao * Date 2022/12/1 * Description 同步等待多个线程准备完毕 */ @Slf4j public class CountDownlatchDemo { public static void main(String[] args) throws InterruptedException { RestTemplate restTemplate = new RestTemplate(); log.debug("begin"); ExecutorService service = Executors.newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(4); service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/order/{1}", Map.class, 1); latch.countDown(); }); service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/product/{1}", Map.class, 1); latch.countDown(); }); service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/product/{1}", Map.class, 2); latch.countDown(); }); service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/logistics/{1}", Map.class, 1); latch.countDown(); }); latch.await(); log.debug("执行完毕"); service.shutdown(); } } ``` + **future实现** ```java /** * Created by lilinchao * Date 2022/12/1 * Description 同步等待多个线程准备完毕 */ @Slf4j public class Main { public static void main(String[] args) throws InterruptedException { RestTemplate restTemplate = new RestTemplate(); log.debug("begin"); ExecutorService service = Executors.newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(4); Future
> f1 = service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/order/{1}", Map.class, 1); return r; }); Future
> f2 = service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/product/{1}", Map.class, 1); return r; }); Future
> f3 = service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/product/{1}", Map.class, 2); return r; }); Future
> f4 = service.submit(() -> { Map
r = restTemplate.getForObject("http://localhost:8080/logistics/{1}", Map.class, 1); return r; }); System.out.println(f1.get()); System.out.println(f2.get()); System.out.println(f3.get()); System.out.println(f4.get()); log.debug("执行完毕"); service.shutdown(); } } ``` **运行结果** ``` 19:51:39.711 c.TestCountDownLatch [main] - begin {total=2300.00, id=1} {price=300, name=小爱音箱, id=1} {price=2000, name=小米手机, id=2} {name=中通快递, id=1} 19:51:42.407 c.TestCountDownLatch [main] - 执行完毕 ``` **结果分析** `CountDownLatch`和`Future`都可以实现,但是各有各的好处,future更适合需要获取结果,进行合并操作的业务逻辑。 *附参考文章链接* *《黑马程序员之并发编程》*
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2660.html
上一篇
56.Semaphore介绍
下一篇
58.CyclicBarrier介绍
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
43
标签云
Spark
Spark Streaming
线程池
Stream流
ClickHouse
正则表达式
散列
SpringCloud
前端
Shiro
二叉树
Flink
Linux
VUE
Java工具类
Kafka
数学
HDFS
Hadoop
BurpSuite
设计模式
Http
JavaWeb
稀疏数组
MyBatis-Plus
Golang基础
持有对象
Hbase
JavaScript
序列化和反序列化
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞