Java线程池创建的五种方法及深度解析
本文详细解析了Java中创建线程池的五种方法:Executors工厂类提供的四种预设线程池(CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor)以及通过ThreadPoolExecutor的自定义创建方式。文章深入剖析了各方法的实现原理、参数配置及适用场景,并通过源码分析揭示了Executors预设方法的潜在风险——可能导致内存溢出或线程资源耗尽。最后,结合阿里巴巴开发规范,强调在生产环境中应优先使用ThreadPoolExecutor进行精细化配置,确保线程池行为可控、资源安全。
博主博客
目录
概述
Java中创建线程池主要分为两类方法:
- 通过Executors工厂类:提供4种预设线程池配置
- 通过ThreadPoolExecutor类:完全自定义线程池参数
一、Executors工厂类提供的四种方法
1. newCachedThreadPool - 可缓存线程池
创建一个可根据需要创建新线程的线程池,空闲线程默认保留60秒。
特点:
- 线程数无上限(理论最大Integer.MAX_VALUE)
- 空闲线程超时自动回收
- 适合执行大量短期异步任务
private static void createCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
执行效果:每个任务可能由不同线程执行,线程会复用但也会不断新建。
2. newFixedThreadPool - 固定大小线程池
创建固定线程数量的线程池,超出的任务在队列中等待。
特点:
- 线程数量固定
- 无界任务队列(LinkedBlockingQueue)
- 适合负载较重的服务器场景
private static void createFixedThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
执行效果:最多只有3个线程工作,任务按提交顺序执行。
3. newScheduledThreadPool - 周期性任务线程池
创建支持定时及周期性任务执行的线程池。
特点:
- 支持延迟执行和定期执行
- 适合定时任务、周期任务场景
private static void createScheduledThreadPool() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
System.out.println(new Date() + " 提交任务");
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.schedule(() -> {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 3, TimeUnit.SECONDS);
}
}
执行效果:任务延迟3秒后开始执行,按固定频率调度。
4. newSingleThreadExecutor - 单线程线程池
创建只有一个工作线程的线程池,保证任务按顺序执行。
特点:
- 单工作线程
- 无界任务队列
- 保证任务执行顺序
private static void createSingleThreadPool() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
执行效果:所有任务由同一个线程按顺序执行。
二、ThreadPoolExecutor自定义创建
构造方法详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1. 七大参数说明
| 参数名 | 说明 | 建议配置 |
|---|---|---|
| corePoolSize | 核心线程数,线程池长期保持的线程数量 | CPU密集型:N+1 IO密集型:2N+1 |
| maximumPoolSize | 最大线程数,线程池允许的最大线程数量 | 根据系统负载和资源决定 |
| keepAliveTime | 空闲线程存活时间,非核心线程空闲时的存活时间 | 根据任务特性设置,通常60s |
| unit | 时间单位 | TimeUnit.SECONDS/MILLISECONDS等 |
| workQueue | 任务队列,存储等待执行的任务 | 根据需求选择不同类型的阻塞队列 |
| threadFactory | 线程工厂,用于创建新线程 | 可自定义线程名称、优先级等 |
| handler | 拒绝策略,当线程池无法处理新任务时的策略 | 根据业务需求选择 |
2. 工作队列类型
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界数组队列,FIFO | 需要控制队列大小,防止资源耗尽 |
| LinkedBlockingQueue | 有界/无界链表队列 | 默认无界,可能引起OOM |
| SynchronousQueue | 不存储元素,直接传递 | 高吞吐量,不缓冲任务 |
| PriorityBlockingQueue | 优先级队列 | 需要按优先级执行任务 |
| DelayedWorkQueue | 延迟队列 | 定时任务场景 |
3. 拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException | 需要明确知道任务被拒绝 |
| CallerRunsPolicy | 由调用线程执行该任务 | 不希望丢弃任务,可接受降级 |
| DiscardOldestPolicy | 丢弃队列最旧的任务,执行当前任务 | 允许丢弃旧任务 |
| DiscardPolicy | 直接丢弃任务,不抛异常 | 允许静默丢弃 |
4. 线程池执行规则
- 创建核心线程:当任务数 < corePoolSize时,创建新线程
- 放入任务队列:当任务数 >= corePoolSize且队列未满时,任务入队
- 创建非核心线程:当队列已满且任务数 < maximumPoolSize时,创建新线程
- 执行拒绝策略:当队列已满且任务数 = maximumPoolSize时,执行拒绝策略
5. 自定义线程池示例
private static void createCustomThreadPool() {
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
10, // 最大线程数
1, TimeUnit.MINUTES, // 空闲线程存活时间
new ArrayBlockingQueue<>(5), // 有界队列,容量5
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 15; i++) {
final int index = i;
try {
executor.execute(() -> {
System.out.println(new Date() + " " +
Thread.currentThread().getName() + " 执行任务" + index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} catch (RejectedExecutionException e) {
System.out.println("任务" + index + "被拒绝:" + e.getMessage());
}
}
// 优雅关闭线程池
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
三、五种方法的实现原理与比较
1. 源码分析
// newCachedThreadPool实现
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// newFixedThreadPool实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// newSingleThreadExecutor实现
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// newScheduledThreadPool实现
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
2. 五种方法对比
| 特性 | CachedThreadPool | FixedThreadPool | SingleThreadExecutor | ScheduledThreadPool | ThreadPoolExecutor |
|---|---|---|---|---|---|
| 核心线程数 | 0 | 固定值 | 1 | 固定值 | 自定义 |
| 最大线程数 | Integer.MAX_VALUE | 同核心线程数 | 1 | Integer.MAX_VALUE | 自定义 |
| 线程存活时间 | 60秒 | 0(永不过期) | 0(永不过期) | 0(永不过期) | 自定义 |
| 工作队列 | SynchronousQueue | LinkedBlockingQueue | LinkedBlockingQueue | DelayedWorkQueue | 自定义 |
| 队列边界 | 无容量 | 无界 | 无界 | 无界 | 可自定义有界 |
| 适用场景 | 短时异步任务 | 长期稳定负载 | 顺序执行任务 | 定时/周期任务 | 所有场景 |
| 风险 | 可能创建大量线程导致OOM | 可能队列堆积导致OOM | 可能队列堆积导致OOM | 可能创建大量线程导致OOM | 可控风险 |
3. 阿里巴巴开发规范建议
【强制】 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors返回的线程池对象的弊端:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
四、线程池的最佳实践建议
1. 参数配置建议
// CPU密集型任务(计算型)
int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
// IO密集型任务(网络/磁盘IO)
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
// 实际项目中推荐配置方式
public class ThreadPoolConfig {
public static ThreadPoolExecutor createCustomExecutor() {
int corePoolSize = 5;
int maxPoolSize = 20;
int queueCapacity = 100;
long keepAliveTime = 60L;
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
new CustomThreadFactory("business-pool"), // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 降级策略
);
}
}
2. 线程工厂自定义
public class CustomThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger threadNumber = new AtomicInteger(1);
public CustomThreadFactory(String poolName) {
namePrefix = poolName + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement());
// 设置线程属性
thread.setDaemon(false);
thread.setPriority(Thread.NORM_PRIORITY);
// 设置异常处理器
thread.setUncaughtExceptionHandler((t, e) -> {
System.err.println("线程" + t.getName() + "发生异常: " + e.getMessage());
});
return thread;
}
}
3. 监控和调试
public class ThreadPoolMonitor {
public static void printThreadPoolStatus(ThreadPoolExecutor executor) {
System.out.println("=== 线程池状态 ===");
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("活动线程数: " + executor.getActiveCount());
System.out.println("最大线程数: " + executor.getMaximumPoolSize());
System.out.println("任务总数: " + executor.getTaskCount());
System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
System.out.println("队列大小: " + executor.getQueue().size());
System.out.println("队列剩余容量: " + executor.getQueue().remainingCapacity());
}
}
总结
关键要点
- Executors工厂方法:适合快速原型开发,但生产环境需谨慎使用
- ThreadPoolExecutor自定义:生产环境推荐方式,可精确控制资源使用
- 参数配置原则:根据任务类型(CPU/IO密集型)合理配置线程数
- 队列选择:有界队列可防止内存溢出,无界队列需评估风险
- 拒绝策略:根据业务容忍度选择合适的拒绝策略
选择建议
- 简单测试/学习:可使用Executors快速创建
- 生产环境:必须使用ThreadPoolExecutor自定义配置
- 定时任务:优先使用ScheduledThreadPoolExecutor
- 高并发场景:使用有界队列+合适的拒绝策略
补充说明
在实际项目中,通常使用Spring的@Async注解或配置ThreadPoolTaskExecutor来管理线程池,这些框架底层也是基于ThreadPoolExecutor的实现,但提供了更方便的配置和管理方式。
记住:合理的线程池配置需要结合具体业务场景、系统资源和性能需求进行调优,没有一成不变的"最佳配置"。
评论