为什么要使用多线程
系统运行最快的部分是CPU,但是要提升系统吞吐能力,需要从系统短板(比如网络延迟、IO)着手。为了最大化的利用CPU资源,就要使用多线程或NIO技术,让CPU忙起来。
为什么要用线程池
* 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗 * 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 * 可以对线程做统一管理。线程是稀缺资源,如果无限制地创建,不仅消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配、调优和监控
为什么配置连接池参数很关键
线程池(ThreadPoolExecutor)是 Java 并发编程的核心工具,用于控制线程的创建、调度和回收。配置不合理会导致:
- 请求处理阻塞
- 内存溢出(OOM)
- CPU 过载,线程频繁上下文切换
- 用户请求超时,甚至系统崩溃
最佳线程数目
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如:
-
线程的CPU时间0.2s,线程IO时间是1s,CPU是4核心,最佳线程数=(1+0.2)/0.2 * 4 = 24 (IO密集型)
-
线程的CPU时间0.5s,线程IO时间是1s,CPU是4核心,最佳线程数=(1+0.5)/0.5 * 4 = 12 (CPU密集型)
结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
实际情况可能更复杂,要考虑硬盘读写、内存速度、网络波动、多服务部署等,最好通过多次压测,评估一个最优的值。
等待队列的大小
等待队列 = (线程池数量/任务执行时间)*最大响应时间
核心参数说明
Java 中的线程池一般通过 ThreadPoolExecutor 创建:
new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程存活时间
timeUnit,
workQueue, // 队列(如 LinkedBlockingQueue)
threadFactory,
rejectedHandler // 拒绝策略
)
重要参数:
| 参数 | 含义 | 建议配置依据 |
|---|---|---|
corePoolSize | 核心线程数 | CPU 核心数/请求量 |
maximumPoolSize | 最大线程数 | 根据系统负载设限 |
workQueue | 等待队列 | 容量不能无限大!需压测决定 |
RejectedExecutionHandler | 拒绝策略 | 选择是否丢弃/异常/回退 |
合理配置公式(经验公式)
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如:
-
线程的CPU时间0.2s,线程IO时间是1s,CPU是4核心,最佳线程数=(1+0.2)/0.2 * 4 = 24 (IO密集型)
-
线程的CPU时间0.5s,线程IO时间是1s,CPU是4核心,最佳线程数=(1+0.5)/0.5 * 4 = 12 (CPU密集型)
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
实际情况可能更复杂,要考虑硬盘读写、内存速度、网络波动、多服务部署等,最好通过多次压测,评估一个最优的值。
等待队列的大小
队列容量建议取决于:
- 接受的最大并发请求数
- 单任务平均执行时长
- 容忍的最大排队等待时间
经验值公式:
等待队列 = (线程池数量/任务执行时间)*最大响应时间
拒绝策略选择建议
| 策略 | 描述 | 推荐场景 |
|---|---|---|
AbortPolicy | 抛出异常 | 强一致性接口,不允许丢任务 |
DiscardPolicy | 丢弃任务 | 日志任务/监控等可容忍丢失 |
CallerRunsPolicy | 当前线程执行 | 控制流量,降级处理 |
DiscardOldestPolicy | 丢弃队首任务 | 优先新任务,短平快任务适用 |
Spring Boot 中线程池配置示例
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(16);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(2000);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("order-async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
常见误区
- ❌ 队列设置过大(如无限队列)→ 内存暴涨
- ❌ maxPoolSize > DB 连接池数 → 导致后端瓶颈
- ❌ 忽略拒绝策略 → 容易出现“线程池吞掉异常”
- ❌ 不监控线程池状态 → 无法发现高负载问题
线程池监控指标建议
接入 Micrometer 或 Prometheus 时,建议重点关注:
- 活跃线程数
activeCount - 队列等待数
queueSize - 拒绝计数(RejectedCount)
- 任务执行时间(平均、P95)
总结:线程池配置的四步法
- 明确任务类型(IO/CPU 密集)
- 计算所需线程数(根据核心数 & 并发量)
- 设置合理的队列大小(不能无限)
- 配置拒绝策略 + 监控 + 压测验证