李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
46.异步模式之工作线程
Leefs
2022-11-21 PM
896℃
0条
[TOC] ### 一、定义 让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。 > 例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那 么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message) 注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率 > 例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工。 ### 二、饥饿 > 固定大小线程池会有饥饿现象 **示例** ``` ● 两个工人是同一个线程池中的两个线程 ● 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作 ○ 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待 ○ 后厨做菜:没啥说的,做就是了 ● 比如工人 A 处理了点餐任务,接下来它要等着工人 B 把菜做好,然后上菜,他俩也配合的蛮好 ● 但现在同时来了两个客人,这个时候工人 A 和工人 B 都去处理点餐了,这时没人做饭了,饥饿 ``` **代码示例** ```java import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Created by lilinchao * Date 2022/11/21 * Description 饥饿状态演示 */ @Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { static final List
MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(() -> { //excute执行一个任务,submit又提交一个任务,等于两个任务。一个线程处理点餐,另一个做菜 //两个任务执行不会产生饥饿现象,但是多了就会产生饥饿。 //两个线程都去处理点餐了,没有线程处理做菜了。f.get()无法获取值,陷入死等。但不是死锁。 log.debug("处理点餐..."); Future
f = executorService.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); /* executorService.execute(() -> { log.debug("处理点餐..."); Future
f = executorService.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } });*/ } } ``` + **运行结果** ``` 23:11:04.553 [pool-1-thread-1] DEBUG c.TestDeadLock - 处理点餐... 23:11:04.557 [pool-1-thread-2] DEBUG c.TestDeadLock - 做菜 23:11:04.557 [pool-1-thread-1] DEBUG c.TestDeadLock - 上菜: 辣子鸡丁 ``` + **当代码的注释取消后,可能的输出** ``` 23:12:40.310 [pool-1-thread-1] DEBUG c.TestDeadLock - 处理点餐... 23:12:40.310 [pool-1-thread-2] DEBUG c.TestDeadLock - 处理点餐... ``` **结果分析** 上面的代码可以看到, 服务员先处理点餐, 然后新创建线程处理做菜, 当只有一位客人的时候是可以正常处理的, 但是如果有两位客人, 但是线程池大小只有2, 就会使线程处于饥饿状态, 导致死锁。 #### 解决办法 增加线程池的核心线程数也是一种办法, 但是我们不能一直使用这种方法, 这样会减少CPU的利用率, 因为当客人少的时候, 核心线程还是这么多。 > 解决办法也很简单, 不同的任务使用不同的线程池去处理 : > > - 点餐线程使用点餐线程池去处理 > - 做饭线程使用做饭线程池去处理 **代码示例** ```java import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Created by lilinchao * Date 2022/11/21 * Description 线程饥饿解决办法 */ @Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { static final List
MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService waiterPool = Executors.newFixedThreadPool(1); ExecutorService cookPool = Executors.newFixedThreadPool(1); waiterPool.execute(() -> { log.debug("处理点餐..."); Future
f = cookPool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); waiterPool.execute(() -> { log.debug("处理点餐..."); Future
f = cookPool.submit(() -> { log.debug("做菜"); return cooking(); }); try { log.debug("上菜: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); } } ``` **运行结果** ``` 23:25:01.950 [pool-1-thread-1] DEBUG c.TestDeadLock - 处理点餐... 23:25:01.956 [pool-2-thread-1] DEBUG c.TestDeadLock - 做菜 23:25:01.956 [pool-1-thread-1] DEBUG c.TestDeadLock - 上菜: 烤鸡翅 23:25:01.958 [pool-1-thread-1] DEBUG c.TestDeadLock - 处理点餐... 23:25:01.958 [pool-2-thread-1] DEBUG c.TestDeadLock - 做菜 23:25:01.958 [pool-1-thread-1] DEBUG c.TestDeadLock - 上菜: 辣子鸡丁 ``` **因此,注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率。** ### 三、线程池大小设置 **创建多少线程池合适 ?** - 过小会导致程序不能充分地利用系统资源、容易导致饥饿 - 过大会导致更多的线程上下文切换,占用更多内存 通常根据不同类型 : CPU密集型运算 和 IO密集型运算来进行考虑 #### 3.1 CPU 密集型运算 通常采用 cpu 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费。 #### 3.2 I/O 密集型运算 CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。 经验公式如下 : > 线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间 例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用 + 套用公式 : 4 * 100% * 100% / 50% = 8 例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用 + 套用公式 : 4 * 100% * 100% / 10% = 40
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2592.html
上一篇
45.ThreadPoolExecutor线程池提交和关闭方法介绍
下一篇
47.任务调度线程池介绍
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
持有对象
队列
二叉树
pytorch
数据结构
Flink
国产数据库改造
数学
SpringCloud
线程池
并发线程
SpringBoot
链表
Java阻塞队列
nginx
Linux
Eclipse
SQL练习题
Java编程思想
人工智能
递归
MySQL
SpringCloudAlibaba
FileBeat
正则表达式
Spark RDD
Typora
数据结构和算法
Git
Kafka
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭