异步模式之工作线程

工作线程模式

基本定义

例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)

  • 注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率

例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成服务员(线程池A)与厨师(线程池B)更为合理

饥饿

固定大小线程池会有饥饿现象 :

  • 两个工人是同一个线程池中的两个线程

  • 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作 :

    • 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
    • 后厨做菜:直接做
  • 比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好

  • 但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,饥饿

饥饿的解决办法

饥饿现象的演示
package cn.knightzz.pattern;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
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;


@SuppressWarnings("all")
@Slf4j(topic = "c.DeadLock")
public class DeadLockTest {

    List<String> MENU = Arrays.asList("地三鲜", "辣子鸡", "宫保鸡丁", "红烧肉");
    Random RANDOM = new Random();

    String cooking(){
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }

    @Test
    public void deadLock01() throws IOException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.execute(() ->{
            // 处理点餐
            log.debug("处理点餐!");
            // 新创建线程做菜
            Future<String> future = executorService.submit(() -> {
                log.debug("做菜..");
                return cooking();
            });
            try {
                log.debug("上菜 : {} ", future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() ->{
            // 处理点餐
            log.debug("处理点餐!");
            // 新创建线程做菜
            Future<String> future = executorService.submit(() -> {
                log.debug("做菜..");
                return cooking();
            });
            try {
                log.debug("上菜 : {} ", future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        System.in.read();
    }

}

上面的代码可以看到, 服务员先处理点餐, 然后新创建线程处理做菜, 当只有一位客人的时候可以可以正常处理的, 但是如果有两位客人, 但是线程池大小只有2, 就会饥饿线程, 导致死锁image-20220902095503274

解决办法

增加线程池的核心线程数也是一种办法, 但是我们不能一直使用这种方法, 这样会减少CPU的利用率, 因为当客人少的时候, 核心线程还是这么多

解决办法也很简单, 不同的任务使用不同的线程池去处理 :

  • 点餐线程使用点餐线程池去处理
  • 做饭线程使用做饭线程池去处理
 ExecutorService waiterPool = Executors.newFixedThreadPool(2);
        ExecutorService cookiePool = Executors.newFixedThreadPool(2);

        waiterPool.execute(() ->{
            // 处理点餐
            log.debug("处理点餐!");
            // 新创建线程做菜
            Future<String> future = cookiePool.submit(() -> {
                log.debug("做菜..");
                return cooking();
            });
            try {
                log.debug("上菜 : {} ", future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(() ->{
            // 处理点餐
            log.debug("处理点餐!");
            // 新创建线程做菜
            Future<String> future = cookiePool.submit(() -> {
                log.debug("做菜..");
                return cooking();
            });
            try {
                log.debug("上菜 : {} ", future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        System.in.read();

线程池大小设置

创建多少线程池合适 ?

  • 过小会导致程序不能充分地利用系统资源、容易导致饥饿

  • 过大会导致更多的线程上下文切换,占用更多内存

通常根据不同类型 : CPU密集型运算 和 IO密集型运算

CPU 密集型运算

通常采用 cpu 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因

导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费

IO密集型运算

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兀坐晴窗独饮茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值