在 Java 并发编程中,线程池是绕不开的核心技术,也是面试高频考点。我们日常开发中处理异步任务、并发请求,几乎都离不开线程池。它解决了频繁创建 / 销毁线程的性能开销,还能统一管理线程资源,提升系统稳定性。
这篇博客会从核心原理、使用场景、标准线程池、自定义线程池、实战避坑全维度讲解,带你彻底吃透 Java 线程池。
一、为什么要用线程池?
先想一个问题:如果不使用线程池,每次处理任务都new Thread().start(),会有什么问题?
- 资源耗尽风险:高并发下大量创建线程,线程占用内存,最终会 OOM(内存溢出);
- 性能损耗严重:线程创建和销毁是重量级操作,频繁操作会大幅降低系统效率;
- 线程无法管理:零散线程无法统一控制,容易导致线程泄露、任务堆积。
而线程池的核心价值就是3 点:
- 降低资源消耗:复用线程,避免频繁创建 / 销毁;
- 提高响应速度:任务到达时,直接用已存在的线程执行,无需等待创建;
- 统一管理线程:控制线程数量、任务队列、拒绝策略,提升系统稳定性。
二、线程池核心原理
Java 线程池的设计非常经典,核心流程一句话总结:任务提交 → 检查核心线程数 → 任务队列排队 → 扩容至最大线程 → 触发拒绝策略
完整执行流程:
- 提交任务时,线程池会判断核心线程数是否已满,未满则创建核心线程执行任务;
- 核心线程满了,任务会进入阻塞队列排队等待;
- 阻塞队列满了,线程池会创建非核心线程 执行任务,直到达到最大线程数;
- 最大线程数也满了,触发拒绝策略,处理无法执行的任务;
- 空闲的非核心线程,超过空闲时间后会被回收,核心线程会一直保留。
三、Executor 框架:线程池的顶层设计
Java 提供了java.util.concurrent并发包,通过Executor 框架实现线程池的标准化:
Executor:最顶层接口,仅定义execute()执行任务方法;ExecutorService:线程池核心接口,扩展了submit()、shutdown()等方法;ThreadPoolExecutor:线程池真正的实现类(所有线程池的底层);Executors:工具类,快速创建预配置的线程池。
四、ThreadPoolExecutor 核心参数(必背!)
这是线程池最核心的知识点,所有自定义线程池都基于这个类:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数(常驻线程,默认不回收)
int maximumPoolSize, // 最大线程数(核心+非核心线程总数)
long keepAliveTime, // 非核心线程空闲超时时间
TimeUnit unit, // 超时时间单位
BlockingQueue<Runnable> workQueue, // 任务阻塞队列
ThreadFactory threadFactory, // 线程工厂(自定义线程名、优先级)
RejectedExecutionHandler handler // 拒绝策略
)
关键参数详解
- corePoolSize:核心线程数,建议根据 CPU 核心数设置(IO 密集型 / CPU 密集型);
- maximumPoolSize:线程池能容纳的最大线程数,超过这个数的任务会被拒绝;
- keepAliveTime:非核心线程空闲多久会被回收,核心线程默认不回收;
- workQueue :任务队列,常用 3 种:
ArrayBlockingQueue:有界队列,必须指定容量(推荐生产使用);LinkedBlockingQueue:无界队列,容量无限(容易 OOM,生产慎用);SynchronousQueue:不存储元素,提交任务必须立即分配线程;
- threadFactory:自定义线程名,方便日志排查问题;
- handler :拒绝策略,4 种内置策略:
AbortPolicy:默认策略,直接抛异常(常用);CallerRunsPolicy:让提交任务的线程自己执行(降低并发);DiscardPolicy:直接丢弃任务,不抛异常;DiscardOldestPolicy:丢弃队列最老的任务,重试提交。
五、Executors 快速创建线程池(生产环境禁用!)
Executors提供了 4 种常用线程池,但阿里巴巴开发手册明确禁止生产使用,因为会导致 OOM:
- FixedThreadPool :固定线程数,队列是无界
LinkedBlockingQueue,任务堆积会内存溢出; - SingleThreadExecutor:单线程线程池,同样无界队列,风险同上;
- CachedThreadPool :无核心线程,最大线程数
Integer.MAX_VALUE,无限创建线程 OOM; - ScheduledThreadPool:定时任务线程池,最大线程数无限,风险极高。
❌ 错误示例(生产禁用):
// 无界队列,高并发下OOM
ExecutorService executor = Executors.newFixedThreadPool(5);
六、生产环境最佳实践:自定义线程池
生产环境必须手动创建 ThreadPoolExecutor,指定有界队列、自定义线程名、合理拒绝策略。
1. 自定义线程池完整代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义线程池工具类(生产可用)
*/
public class ThreadPoolUtil {
// CPU核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// 核心线程数:CPU密集型=CPU数+1,IO密集型=CPU数*2
private static final int CORE_POOL_SIZE = CPU_COUNT * 2;
// 最大线程数
private static final int MAX_POOL_SIZE = CPU_COUNT * 4;
// 非核心线程空闲时间
private static final long KEEP_ALIVE_TIME = 60L;
// 自定义线程工厂(设置线程名,方便日志排查)
private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
private final AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "自定义线程池-" + threadNum.getAndIncrement());
// 设置非守护线程
thread.setDaemon(false);
return thread;
}
};
// 有界阻塞队列(容量100,必须指定!防止OOM)
private static final BlockingQueue<Runnable> WORK_QUEUE = new ArrayBlockingQueue<>(100);
// 拒绝策略:调用者执行(最稳妥,不会丢任务)
private static final RejectedExecutionHandler HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();
// 单例线程池
private static volatile ThreadPoolExecutor threadPool;
// 双重校验锁获取线程池实例
public static ThreadPoolExecutor getThreadPool() {
if (threadPool == null) {
synchronized (ThreadPoolUtil.class) {
if (threadPool == null) {
threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
WORK_QUEUE,
THREAD_FACTORY,
HANDLER
);
}
}
}
return threadPool;
}
}
2. 线程池使用示例
public class ThreadPoolTest {
public static void main(String[] args) {
// 获取自定义线程池
ThreadPoolExecutor executor = ThreadPoolUtil.getThreadPool();
// 提交10个任务
for (int i = 1; i <= 10; i++) {
int taskNum = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskNum);
try {
// 模拟业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池(生产环境一般不关闭,服务重启时关闭)
executor.shutdown();
}
}
七、线程池核心方法
execute():提交无返回值的任务,无法判断任务是否执行成功;submit():提交有返回值的任务,返回Future对象,可获取执行结果;shutdown():优雅关闭线程池,不再接受新任务,执行完已有任务;shutdownNow():立即关闭线程池,中断正在执行的任务,清空队列;isTerminated():判断线程池是否完全关闭。
八、线程池参数如何合理设置?
线程池参数不是固定值,要根据任务类型调整:
- CPU 密集型任务 (计算、加密、排序):
- 线程少一点,避免线程切换消耗 CPU:
核心线程数 = CPU核心数 + 1
- 线程少一点,避免线程切换消耗 CPU:
- IO 密集型任务 (网络请求、数据库查询、文件读写):
- 线程多一点,IO 等待时可以处理其他任务:
核心线程数 = CPU核心数 * 2
- 线程多一点,IO 等待时可以处理其他任务:
- 队列必须用有界队列(ArrayBlockingQueue),容量根据业务 QPS 设置;
- 拒绝策略优先用
CallerRunsPolicy,保证任务不丢失。
九、线程池监控(生产必备)
生产环境必须监控线程池状态,提前发现问题,常用监控指标:
ThreadPoolExecutor executor = ThreadPoolUtil.getThreadPool();
// 核心线程数
System.out.println("核心线程数:" + executor.getCorePoolSize());
// 当前活跃线程数
System.out.println("活跃线程数:" + executor.getActiveCount());
// 已完成任务数
System.out.println("完成任务数:" + executor.getCompletedTaskCount());
// 队列大小
System.out.println("队列等待任务数:" + executor.getQueue().size());
十、避坑总结
- 禁止用 Executors 创建线程池,必须自定义 ThreadPoolExecutor;
- 必须使用有界队列,无界队列会导致内存溢出;
- 自定义线程工厂,给线程设置有意义的名字,方便排查问题;
- 合理设置核心线程数,根据 CPU/IO 密集型调整;
- 生产环境不要频繁关闭 / 创建线程池,全局单例使用;
- 做好线程池监控,及时发现任务堆积、线程耗尽问题。