1. 引言
线程池 是用来管理和复用线程的。如果不使用线程池,可能会造成以下问题:
- 每次都得创建新线程,然后重复销毁,浪费性能
- 会无限制地创建线程,造成内存压力
- 线程生命周期难以把控
因此,合理使用线程池是 Java 并发编程的基础之一。
2. 快速创建线程池
使用 Executors 的静态方法可以快速获得线程池实例:
| 方法 | 线程池类型 | 特点 | 适用场景 |
|---|---|---|---|
newFixedThreadPool(int n) |
固定大小 | 核心线程数 = 最大线程数 = n,队列无界 | 负载稳定的长期任务 |
newCachedThreadPool() |
缓存型 | 核心线程数 = 0,最大线程数 = 无限,队列为 SynchronousQueue |
短期、突发的异步任务 |
newSingleThreadExecutor() |
单线程 | 只有一个工作线程,队列无界 | 需要顺序执行的任务(如日志写入) |
newScheduledThreadPool(int n) |
定时任务 | 支持延迟和周期性执行 | 定时任务(如心跳检测) |
示例:
java
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
3. 手动创建线程池(ThreadPoolExecutor)
手动创建线程池具有更高的可操作性和灵活性,推荐在生产环境中使用:
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // 核心线程数(corePoolSize)
5, // 最大线程数(maximumPoolSize)
60L, // 空闲线程存活时间(keepAliveTime)
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 任务队列(有界队列,容量 100)
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(队列满时由提交线程执行)
);
它们都实现了统一的接口 ExecutorService。
4. 核心参数深度解析
4.1 核心线程数(corePoolSize)
计算依据:核心线程数应充分利用 CPU 资源。
- CPU 密集型任务:核心线程数 = CPU 核数
- IO 密集型任务:核心线程数 = CPU 核数 × 2
- 混合型任务 :根据等待时间占比公式计算
核心线程数 = (总响应时间 / CPU 计算时间) × CPU 核数
例如:总耗时 500ms 中 CPU 计算时间为 50ms,IO 等待为 450ms
核心线程数 = (500/50) × 4 核 = 40 - 保守策略:直接设为 CPU 核数(如 4)以避免资源过载
4.2 最大线程数(maximumPoolSize)
这是线程池可以同时运行的最大线程数量。当工作队列满了,且当前运行的线程数少于最大线程数时,线程池会创建新的线程来处理任务。最大线程数包含核心线程数。
- 公式推导 :
最大线程数 = ((线程等待时间 + CPU 时间) / CPU 时间) × CPU 核数
代入数据:((450 + 50) / 50) × 4 = 40 - 安全余量:考虑突发流量,建议设为 50
- 经验值:通常为核心线程数的 5--10 倍(混合型任务)
4.3 任务队列
| 实现类 | 特点 | 常用场景 |
|---|---|---|
ArrayBlockingQueue |
有界队列,基于数组,必须指定容量 | 任务量可控,需限制内存使用 |
LinkedBlockingQueue |
有界/无界队列,基于链表,容量默认为 Integer.MAX_VALUE |
任务量大,生产速度不确定 |
SynchronousQueue |
不存储元素,每个插入操作必须等待一个取出操作 | 传递性任务,要求任务立即被处理 |
队列大小经验公式:核心线程每秒能处理的任务数 × 2
4.4 拒绝策略(RejectedExecutionHandler)
当任务队列满且工作线程数达到最大值时,新提交的任务会被拒绝。Java 提供四种内置策略:
| 策略 | 行为 | 风险 |
|---|---|---|
AbortPolicy(默认) |
抛出 RejectedExecutionException |
可能导致任务丢失,需捕获异常 |
CallerRunsPolicy |
由提交任务的线程自己执行 | 阻塞提交者,但保证任务不丢失 |
DiscardPolicy |
直接丢弃新任务,无任何提示 | 可能丢失数据(慎用) |
DiscardOldestPolicy |
丢弃队列中最老的任务,然后尝试提交新任务 | 可能丢失未处理的历史任务 |
5. 线程池常用方法
5.1 提交任务(最常用)
| 方法 | 返回值 | 说明 |
|---|---|---|
execute(Runnable) |
void |
提交无返回值的任务 |
submit(Runnable) |
Future<?> |
提交无返回值任务,可通过 Future 获取执行状态 |
submit(Callable<T>) |
Future<T> |
提交有返回值的任务(最常用) |
invokeAll(Collection<Callable>) |
List<Future<T>> |
批量提交,等待所有任务完成 |
invokeAny(Collection<Callable>) |
T |
批量提交,任意一个完成即返回(其他取消) |
5.2 销毁与关闭
| 方法 | 行为 | 是否等待任务完成 |
|---|---|---|
shutdown() |
拒绝新任务,已提交的任务继续执行 | 等待完成 |
shutdownNow() |
尝试中断正在执行的任务,返回未执行的任务列表 | 强制中断 |
awaitTermination(timeout, unit) |
阻塞等待,直到线程池完全终止或超时 | 配合 shutdown() 使用 |
5.3 状态查询
| 方法 | 返回值 | 说明 |
|---|---|---|
isShutdown() |
boolean |
是否已调用 shutdown() 或 shutdownNow() |
isTerminated() |
boolean |
是否已彻底终止(所有任务完成) |
awaitTermination(timeout, unit) |
boolean |
阻塞等待,直到终止或超时 |
6. 最佳实践:优雅使用线程池
java
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建线程池(手动创建,不要用 Executors)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
4, 8, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 2. 提交有返回值的任务
Future<String> future = pool.submit(() -> {
Thread.sleep(2000);
return "任务结果";
});
try {
String result = future.get(3, TimeUnit.SECONDS);
System.out.println(result);
} catch (TimeoutException e) {
System.out.println("任务超时");
future.cancel(true); // 取消任务
} catch (Exception e) {
e.printStackTrace();
}
// 3. 优雅关闭
pool.shutdown();
try {
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}