让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现 就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那 么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成 服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工
固定大小线程池会有饥饿现象
两个工人是同一个线程池中的两个线程
他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好
但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,饥饿
public class Test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(()->{
System.out.println("点餐中...");
Future<String> future = executorService.submit(() -> {
return "宫保鸡丁1";
});
try {
String s = future.get();
System.out.println("上菜"+s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.submit(()->{
System.out.println("点餐中...");
Future<String> future = executorService.submit(() -> {
return "宫保鸡丁2";
});
try {
String s = future.get();
System.out.println("上菜"+s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
}
运行结果如下: 点餐中... 点餐中... 上菜宫保鸡丁2 上菜宫保鸡丁1
如果修改核心线程为3,即修改代码中
ExecutorService executorService = Executors.newFixedThreadPool(3);
则运行结果如下: 点餐中... 点餐中... 上菜宫保鸡丁2 上菜宫保鸡丁1
解决方法可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程池,例如:
public class Test {
public static void main(String[] args) {
ExecutorService orderExecutorService = Executors.newFixedThreadPool(1);
ExecutorService cookExecutorService = Executors.newFixedThreadPool(1);
orderExecutorService.submit(()->{
System.out.println("点餐中...");
Future<String> future = cookExecutorService.submit(() -> {
return "宫保鸡丁1";
});
try {
String s = future.get();
System.out.println("上菜"+s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
orderExecutorService.submit(()->{
System.out.println("点餐中...");
Future<String> future = cookExecutorService.submit(() -> {
return "宫保鸡丁2";
});
try {
String s = future.get();
System.out.println("上菜"+s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
}
运行结果如下:
点餐中... 上菜宫保鸡丁1 点餐中... 上菜宫保鸡丁2
线程池的大小应根据具体的应用场景和系统需求来确定。以下是一些建议供参考:
总之,确定线程池大小需要综合考虑系统资源、任务类型、响应时间和任务排队策略等因素,并进行实际的性能测试和调优。根据实际情况不断调整线程池的大小,以达到最佳的性能和资源利用率。
通常采用 cpu 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因 导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
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
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有