线程池是 Java 中处理多线程的强大工具,但它不仅仅是“直接用就完事”的工具。
很多小伙伴在用线程池时,因为配置不当或忽略细节,踩过许多坑。
今天跟大家一起聊聊线程池中容易踩的 10 个坑,以及如何避免这些坑,希望对你会有所帮助。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
许多初学者在创建线程池时,直接使用 Executors
提供的快捷方法:
ExecutorService executor = Executors.newFixedThreadPool(10);
- 无界队列 :
newFixedThreadPool
使用的队列是 LinkedBlockingQueue
,它是无界队列,任务堆积可能会导致内存溢出。 - 线程无限增长 :
newCachedThreadPool
会无限创建线程,在任务量激增时可能耗尽系统资源。
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i 1000000; i++) {
executor.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
任务数远大于线程数,导致任务无限堆积在队列中,最终可能导致 OutOfMemoryError
。
使用 ThreadPoolExecutor
,并明确指定参数:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/yudao-cloud
- 视频教程:https://doc.iocoder.cn/video/
很多人随意配置线程池参数,比如核心线程数 10,最大线程数 100,看起来没问题,但这可能导致性能问题或资源浪费。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // 核心线程数
100, // 最大线程数
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10)
);
for (int i = 0; i 1000; i++) {
executor.submit(() -> {
try {
Thread.sleep(5000); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
这种配置在任务激增时,会创建大量线程,系统资源被耗尽。
根据任务类型选择合理的线程数:
- CPU 密集型 :线程数建议设置为
CPU 核心数 + 1
。 - IO 密集型 :线程数建议设置为
2 * CPU 核心数
。
示例:
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCores + 1,
cpuCores + 1,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(50)
);
任务队列直接影响线程池的行为。如果选错队列类型,会带来很多隐患。
- 优先级队列 :容易导致高优先级任务频繁抢占低优先级任务。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
for (int i = 0; i 100000; i++) {
executor.submit(() -> System.out.println(Thread.currentThread().getName()));
}
改进方法 :用有界队列,避免任务无限堆积。
new ArrayBlockingQueue<>(100);
有些小伙伴用完线程池后,忘记调用 shutdown()
,导致程序无法正常退出。
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("任务执行中..."));
// 线程池未关闭,程序一直运行
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
当任务队列满时,线程池会触发拒绝策略,很多人不知道默认策略(AbortPolicy
)会直接抛异常。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
1,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy() // 默认策略
);
for (int i = 0; i 10; i++) {
executor.submit(() -> System.out.println("任务"));
}
执行到第四个任务时会抛出 RejectedExecutionException
。
CallerRunsPolicy
:提交任务的线程自己执行。DiscardOldestPolicy
:丢弃最老的任务。
线程池中的任务抛出异常时,线程池不会直接抛出,导致很多问题被忽略。
executor.submit(() -> {
throw new RuntimeException("任务异常");
});
executor.submit(() -> {
try {
throw new RuntimeException("任务异常");
} catch (Exception e) {
System.err.println("捕获异常:" + e.getMessage());
}
});
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, e) -> {
System.err.println("线程异常:" + e.getMessage());
});
return t;
};
如果线程池中的任务是阻塞的(如文件读写、网络请求),核心线程会被占满,影响性能。
executor.submit(() -> {
Thread.sleep(10000); // 模拟阻塞任务
});
线程池不是万能的,某些场景直接使用 new Thread()
更简单。
一个简单的短期任务:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("执行任务"));
executor.shutdown();
这种情况下,用线程池反而复杂。
new Thread(() -> System.out.println("执行任务")).start();
很多人用线程池后,不监控其状态,导致任务堆积、线程耗尽的问题被忽略。
System.out.println("核心线程数:" + executor.getCorePoolSize());
System.out.println("队列大小:" + executor.getQueue().size());
System.out.println("已完成任务数:" + executor.getCompletedTaskCount());
结合监控工具(如 JMX、Prometheus),实现实时监控。
有些人在线程池设计时忽略了参数调整的必要性,导致后期性能优化困难。
executor.setCorePoolSize(20);
executor.setMaximumPoolSize(50);
实时调整线程池参数,能适应业务的动态变化。
线程池是强大的工具,但如果我们日常工作中用得不好也非常容易踩坑。
这篇文章通过实际代码示例,我们可以清楚看到线程池的问题所在及改进方法。
希望这些内容能帮你避免踩坑,写出高质量的线程池代码!
线程池用得好,效率杠杠的;用得不好,程序天天崩!