线程池

235 阅读4分钟

线程池的核心参数

  • coreThreadNum 核心线程数
  • maxThreadNum 最大线程数
  • keepAliveTime 线程空闲的最大时间
  • timeUnit keepAliveTime 时间单位
  • queue 队列
  • threadFactory 线程工厂
  • rejectPolicy 拒绝策略

线程池 处理 任务的流程

  • 当前工作线程数量 < 核心线程数 则 创建线程处理任务
  • 当前工作线程数量 > 核心线程数
    • 任务队列不满 则 将任务加入任务队列
    • 任务队列满
      • 当前工作线程数量 > 最大线程数 则 执行拒绝策略
      • 当前工作线程数量 < 最大线程数 则 创建非核心线程数 执行任务

线程池线程数到该如何设置呢?

  • 任务的分类
    • cpu 密集型
    • 线程数 = 核心数 + 1

    • io 密集型
    • 线程数 = 核心数 / (1 - 阻塞系数)

    • 阻塞系数 = IO等待时间 / IO任务执行总时间

    • cpu和io混合型
    • (IO阻塞时间/CPU执行时间 + 1)* 核心数

实际的代码执行

8CPU 16G macOS Ventura13.6

  • IO 密集
public static void main(String[] args) {
    final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("job-pool-dataTaskExecutor-%d ")
            .build();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(9, 9, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(8000), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    ThreadHelper threadHelper = new ThreadHelper();
    ArrayList<Object> datas = new ArrayList<>();
    for (int i = 0; i < 8000; i++) {
        datas.add(i);
    }
    long start = System.currentTimeMillis();
    System.out.println("start "+ start);
    threadHelper.go(datas,(data)->{
        try {
            TimeUnit.MILLISECONDS.sleep(100)
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }, threadPoolExecutor);
    System.out.println("cost " + (System.currentTimeMillis() - start) + " ms");
}
  • 结果(执行8000个任务 每个任务耗时0.1s 8cpu)
  • 核心线程数=9 最大线程数=9 队列=8000 耗时90秒
  • 核心线程数=36 最大线程数=36 队列=8000 耗时23秒
  • 核心线程数=80 最大线程数=80 队列=8000 耗时10秒
  • 核心线程数=100 最大线程数=100 队列=8000 耗时8秒
  • 核心线程数=120 最大线程数=120 队列=8000 耗时6.9秒
  • 核心线程数=160 最大线程数=160 队列=8000 耗时5.2秒
  • 核心线程数=320 最大线程数=320 队列=8000 耗时2.6秒
  • 核心线程数=640 最大线程数=640 队列=8000 耗时1.3秒
  • 核心线程数=800 最大线程数=800 队列=8000 耗时1.09秒
  • 核心线程数=900 最大线程数=900 队列=8000 耗时1.06秒
  • 800 = 8/(1 - 阻塞系数),阻塞系数 = IO等待时间 / IO任务执行总时间 = 0.09
  • cpu密集
public static void main(String[] args) {
    final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("job-pool-dataTaskExecutor-%d ")
            .build();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(9, 9, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(8000), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    ThreadHelper threadHelper = new ThreadHelper();
    ArrayList<Object> datas = new ArrayList<>();
    for (int i = 0; i < 8000; i++) {
        datas.add(i);
    }
    long start = System.currentTimeMillis();
    System.out.println("start "+ start);
    threadHelper.go(datas,(data)->{
        try {
            long l = System.currentTimeMillis();
            while (true){
                if (System.currentTimeMillis() - l > 100){
                    break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }, threadPoolExecutor);
    System.out.println("cost " + (System.currentTimeMillis() - start) + " ms");
}
  • 结果(执行8000个任务 每个任务耗时0.1s 8cpu)
  • 核心线程数=9 最大线程数=9 队列=8000 耗时90秒
  • 核心线程数=36 最大线程数=36 队列=8000 耗时23秒
  • 核心线程数=80 最大线程数=80 队列=8000 耗时15秒
  • 核心线程数=100 最大线程数=100 队列=8000 耗时15秒
  • 核心线程数=120 最大线程数=120 队列=8000 耗时15秒
  • 线程数 = 核心数 + 1 理论上是9 才对,实际上
  • 线程数 = 核心数 * 10 = 80 达到了最优

总结

  • CPU密集型 线程数 = 核心数 + 1 的公式与实际的模拟的场景结果 线程数 = 核心数 * 10 相差较大

  • 实际上线程数只能根据实际情况进行调整,但是初始化预估可以参数模拟场景的结果

  • 在模拟环境下,初始化预估步骤

    • 单个任务执行的总时间
    • 单个任务IO阻塞等待时间
    • 单个任务cpu执行时间
    • 阻塞系数
    • 核心数
    • 总任务数
    • cpu密集最大线程数(cpu10)、IO密集最大线程数(cpu100)
  • 举个列子:

  • 在模拟环境下,初始化预估步骤

    • 单个任务执行的总时间 1
    • 单个任务IO阻塞等待时间 0.5
    • 阻塞系数 0.5
    • 核心数 8
    • 总任务数 1000
    • cpu密集最大线程数(cpu10) = 80、IO密集最大线程数(cpu100)= 800
  • 核心线程数按照如上的计算预估

  • 一直保持最优核心线程数的并发执行,就需要将队列的大小设置到保证所有任务要么正在使用核心线程数执行,要么任务进入队列,此时如果内存达到瓶颈,则按照内存情况设置到最大,最大线程数的设置保证任务都会执行,而不走拒绝策略。