在实际开发中,线程池几乎是每个 Java 后端绕不开的组件。但真正让人困惑的往往不是怎么用线程池,而是——线程数到底该怎么配。
有人按 CPU 核数来,有人直接乘 2,还有人干脆拍脑袋设一个固定值。这些做法在某些场景下'看起来能跑',但在 IO 较多或混合型任务中,往往会带来性能下降、请求堆积,甚至线程池耗尽的问题。
本文结合常见的 IO 密集型、CPU 密集型以及混合型任务,梳理线程池线程数配置的基本思路,并给出可参考的计算方式,帮助你在不同场景下做出更合理的选择。
1. 按照任务类型对线程池进行分类
在讨论线程数之前,首先需要明确一点:线程数的配置和任务类型是强相关的。使用标准构造器 ThreadPoolExecutor 创建线程池时,会涉及线程数的配置,而线程数的配置与异步任务类型是分不开的。这里将线程池的异步任务大致分为以下三类:
- IO 密集型任务
此类任务主要是执行 IO 操作。由于执行 IO 操作的时间较长,导致 CPU 的利用率不高,这类任务 CPU 常处于空闲状态。Netty 的 IO 读写操作为此类任务的典型例子。
- CPU 密集型任务
此类任务主要是执行计算任务。由于响应时间很快,CPU 一直在运行,这种任务 CPU 的利用率很高。
- 混合型任务
此类任务既要执行逻辑计算,又要进行 IO 操作(如 RPC 调用、数据库访问)。相对来说,由于执行 IO 操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的 CPU 利用率也不是太高。Web 服务器的 HTTP 请求处理操作为此类任务的典型例子。
一般情况下,针对以上不同类型的异步任务需要创建不同类型的线程池,并进行针对性的参数配置。
2. 为 IO 密集型任务确定线程数
由于 IO 密集型任务的 CPU 使用率较低,导致线程空余时间很多,因此通常需要开 CPU 核心数两倍的线程。当 IO 线程空闲时,可以启用其他线程继续使用 CPU,以提高 CPU 的使用率。
接下来为 IO 密集型任务创建了一个简单的参考线程池,具体代码如下:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadUtil {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int THREAD_COUNT = Math.max(2, CPU_COUNT);
private static final int QUEUE_COUNT = 128;
;
{
(
THREAD_COUNT, THREAD_COUNT, KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS, <>(QUEUE_COUNT),
.AbortPolicy());
}
}


