"不就是个线程池吗?核心线程数设大点不就行了?" ------ 如果你这样想,恭喜,线上故障正在向你招手!
第一宗罪:盲目设大核心线程数
错误示范:
java
java
// "反正线程越多性能越好" → 灾难的开始
ThreadPoolExecutor executor = new ThreadPoolExecutor(
100, // corePoolSize:你以为的"充分资源"
200, // maximumPoolSize:留点余地
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
血泪教训:
java
arduino
// 某电商平台大促期间CPU 100%的真相
// 监控发现:线程数峰值达到200,但CPU使用率100%
// 根本原因:太多线程竞争CPU,上下文切换开销吃掉所有资源
// 正确思路:根据业务类型设置
- CPU密集型:核心线程数 = CPU核数 + 1
- IO密集型:核心线程数 = CPU核数 × 2
- 混合型:通过压测找到最佳值
第二宗罪:队列长度设置不当
经典反模式:
java
arduino
// 情况1:无界队列 → 内存溢出警告!
new LinkedBlockingQueue<>(); // 默认Integer.MAX_VALUE
// 情况2:队列过小 → 频繁创建销毁线程
new ArrayBlockingQueue<>(10); // 来11个任务就创建新线程
队列选择的艺术:
java
java
// 1. 需要控制吞吐量 → LinkedBlockingQueue
// 2. 需要快速响应 → SynchronousQueue(不缓存,直接传递)
// 3. 需要优先级调度 → PriorityBlockingQueue
// 4. 需要延迟执行 → DelayQueue
// 实际案例:订单处理系统
ThreadPoolExecutor orderExecutor = new ThreadPoolExecutor(
8, 16, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(500), // 根据系统承载能力设置
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时让调用线程执行
);
第三宗罪:忽略拒绝策略的重要性
四种拒绝策略的适用场景:
java
arduino
// 1. AbortPolicy(默认):直接抛出异常
// 适用:严格要求数据一致性的场景
new ThreadPoolExecutor.AbortPolicy();
// 2. CallerRunsPolicy:让调用线程执行任务
// 适用:需要保证每个任务都被执行的场景
new ThreadPoolExecutor.CallerRunsPolicy();
// 3. DiscardPolicy:静默丢弃任务
// 适用:日志记录、统计信息等可丢失的场景
new ThreadPoolExecutor.DiscardPolicy();
// 4. DiscardOldestPolicy:丢弃队列中最老的任务
// 适用:实时性要求高,老数据可丢弃的场景
new ThreadPoolExecutor.DiscardOldestPolicy();
自定义拒绝策略的实战案例:
java
java
// 电商订单场景:既不能丢订单,又不能拖垮系统
public class OrderRejectedExecutionHandler implements RejectedExecutionHandler {
private static final Logger logger = LoggerFactory.getLogger(OrderRejectedExecutionHandler.class);
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (r instanceof OrderTask) {
OrderTask task = (OrderTask) r;
// 1. 记录到数据库,后续补偿处理
saveToRetryQueue(task);
// 2. 告警通知
sendAlert("订单处理线程池饱和,订单ID:" + task.getOrderId());
// 3. 监控指标
Metrics.counter("threadpool.rejected").increment();
}
}
}
第四宗罪:线程工厂使用不当
不要用默认线程工厂:
java
java
// 问题:出问题时找不到是谁创建的线程
Executors.defaultThreadFactory(); // 创建的线程名:pool-1-thread-1
// 正确做法:自定义线程工厂
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public NamedThreadFactory(String poolName) {
namePrefix = poolName + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
// 设置异常处理器,避免异常吞掉
t.setUncaughtExceptionHandler(new ThreadUncaughtExceptionHandler());
return t;
}
}
第五宗罪:忘记监控线程池状态
线上故障现场 :
"系统突然变慢,查了半天才发现线程池队列积压了5000个任务,但开发人员完全不知道!"
监控方案:
java
java
@Component
public class ThreadPoolMonitor {
@Scheduled(fixedRate = 5000) // 每5秒监控一次
public void monitor() {
for (ThreadPoolExecutor executor : getAllExecutors()) {
int corePoolSize = executor.getCorePoolSize();
int activeCount = executor.getActiveCount();
long completedTaskCount = executor.getCompletedTaskCount();
int queueSize = executor.getQueue().size();
// 告警条件
if (queueSize > 1000) {
sendAlert("线程池" + executor + "队列积压:" + queueSize);
}
if ((double) activeCount / corePoolSize > 0.8) {
sendAlert("线程池" + executor + "使用率超过80%");
}
}
}
}
Spring Boot Actuator集成:
yaml
yaml
management:
endpoints:
web:
exposure:
include: threadpools
endpoint:
threadpools:
enabled: true
第六宗罪:线程池隔离不到位
所有业务共用一个线程池的灾难:
java
typescript
// 反例:订单、消息、报表都用同一个线程池
@Bean
public ThreadPoolExecutor commonExecutor() {
return new ThreadPoolExecutor(20, 50, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
}
// 结果:报表导出耗时长,拖垮订单处理
正确的线程池隔离:
java
java
@Configuration
public class ThreadPoolConfig {
// 订单处理:高优先级,快速响应
@Bean("orderExecutor")
public ThreadPoolExecutor orderExecutor() {
return new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200), // 有界队列,快速响应
new NamedThreadFactory("order"),
new OrderRejectedExecutionHandler());
}
// 报表导出:低优先级,可容忍延迟
@Bean("reportExecutor")
public ThreadPoolExecutor reportExecutor() {
return new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 无界队列,可堆积
new NamedThreadFactory("report"),
new ThreadPoolExecutor.DiscardPolicy());
}
// 消息发送:中等优先级
@Bean("messageExecutor")
public ThreadPoolExecutor messageExecutor() {
return new ThreadPoolExecutor(8, 16, 30, TimeUnit.SECONDS,
new SynchronousQueue<>(), // 不缓存,直接传递
new NamedThreadFactory("message"),
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
第七宗罪:优雅关闭考虑不周
直接System.exit(0)的后果:
- 队列中的任务丢失
- 正在执行的任务被强制中断
- 数据不一致
正确的关闭姿势:
java
scss
@Component
public class ThreadPoolShutdown {
@PreDestroy
public void gracefulShutdown() {
// 1. 停止接收新任务
executor.shutdown();
try {
// 2. 等待现有任务完成,最多等30分钟
if (!executor.awaitTermination(30, TimeUnit.MINUTES)) {
// 3. 强制关闭
List<Runnable> droppedTasks = executor.shutdownNow();
logger.warn("线程池强制关闭,丢弃任务数:" + droppedTasks.size());
// 4. 再次等待
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
logger.error("线程池未能正常关闭");
}
}
} catch (InterruptedException e) {
// 5. 重新尝试强制关闭
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
🛠️ 实战:一个生产级的线程池配置
java
typescript
@Bean("businessExecutor")
public ThreadPoolExecutor businessExecutor() {
int corePoolSize = Runtime.getRuntime().availableProcessors();
int maxPoolSize = corePoolSize * 2;
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60L, TimeUnit.SECONDS,
new ResizableLinkedBlockingQueue<>(1000), // 可动态调整的队列
new NamedThreadFactory("business"),
new BusinessRejectedExecutionHandler()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
// 记录任务开始时间
MDC.put("traceId", UUID.randomUUID().toString());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
// 清理上下文,记录监控指标
MDC.clear();
Metrics.timer("task.duration").record(...);
}
};
}
📊 线程池参数调优 checklist
- 核心线程数:根据业务类型设置
- 最大线程数:留有缓冲,但不能太大
- 队列选择:根据对响应时间的要求
- 拒绝策略:根据业务重要性选择
- 线程命名:便于问题排查
- 异常处理:避免异常被吞掉
- 监控告警:及时发现问题
- 优雅关闭:保证数据一致性
互动 :
你在使用线程池时踩过哪些坑?有没有因为线程池配置不当导致过线上故障?分享你的经验,让大家一起避坑!
记住:线程池不是银弹,合理的配置比盲目调参更重要。在生产环境中,一定要结合监控和压测来验证配置效果。