Java 线程池是并发编程中的核心组件,合理配置线程池参数对系统性能、资源利用率和稳定性至关重要。本文将从 线程池核心参数详解、工作流程、常见线程池类型、最佳实践建议 以及 实战示例 几个方面系统讲解 Java 线程池的使用。
一、线程池核心参数详解
Java 中通过 ThreadPoolExecutor 类创建线程池,其构造函数如下:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
1. corePoolSize(核心线程数)
- 线程池中长期存活的线程数量(即使空闲也不会被回收,除非设置了 allowCoreThreadTimeOut(true))。
- 当新任务提交时,如果当前线程数 < corePoolSize,即使有空闲线程,也会创建新线程处理任务。
2. maximumPoolSize(最大线程数)
- 线程池允许创建的最大线程数量。
- 当任务队列已满,且当前线程数 < maximumPoolSize 时,会创建非核心线程处理任务。
3. keepAliveTime + unit(线程空闲存活时间)
- 非核心线程在空闲状态下保持存活的时间。
- 如果设置了 allowCoreThreadTimeOut(true),该策略也适用于核心线程。
4. workQueue(任务队列)
- 用于缓存等待执行的任务。常见实现:
- ArrayBlockingQueue:有界队列,需指定容量。
- LinkedBlockingQueue:无界队列(默认容量为 Integer.MAX_VALUE,慎用!)。
- SynchronousQueue:不存储任务,直接交给线程执行(常用于 CachedThreadPool)。
- PriorityBlockingQueue:支持优先级排序的无界队列。
5. ThreadFactory(线程工厂)
- 用于创建新线程,可自定义线程名称、优先级、是否守护线程等,便于调试和监控。
6. RejectedExecutionHandler(拒绝策略)
- 当线程池已满(达到 maximumPoolSize 且队列也满)时,对新任务的处理策略:
- AbortPolicy(默认):抛出 RejectedExecutionException。
- CallerRunsPolicy:由提交任务的线程(调用者线程)执行该任务。
- DiscardPolicy:静默丢弃任务。
- DiscardOldestPolicy:丢弃队列中最老的任务,尝试重新提交新任务。
- 自定义策略:实现 RejectedExecutionHandler 接口。
二、线程池工作流程
- 提交任务。
- 如果当前线程数 < corePoolSize → 创建核心线程执行任务。
- 否则,尝试将任务加入 workQueue。
- 若入队成功,等待线程处理。
- 若队列已满,且当前线程数 < maximumPoolSize → 创建非核心线程执行任务。
- 若线程数已达 maximumPoolSize 且队列满 → 触发拒绝策略。
注意:使用无界队列(如 LinkedBlockingQueue 无参构造)时,maximumPoolSize 实际无效,因为任务永远不会入队失败。
三、JDK 提供的常见线程池(不推荐直接使用)
线程池类型 创建方式 特点 风险 newFixedThreadPool 固定核心/最大线程数,无界队列 线程数固定,任务排队 OOM 风险(队列无限增长) newCachedThreadPool 核心=0,最大=Integer.MAX,SynchronousQueue 动态扩缩容 线程数爆炸,CPU/内存耗尽 newSingleThreadExecutor 单线程,无界队列 顺序执行 同 FixedThreadPool 的队列风险 newScheduledThreadPool 支持定时/周期任务 适用于定时任务 同样需注意队列和线程数
建议:生产环境应使用 ThreadPoolExecutor 手动创建,明确控制参数。
四、线程池参数设置最佳实践
1. CPU 密集型任务
- 特点:计算为主,CPU 利用率高。
- 线程数建议:N + 1(N = CPU 核心数)
- 防止因线程阻塞导致 CPU 空闲。
2. IO 密集型任务
- 特点:大量等待(如网络、磁盘 IO)。
- 线程数建议:2N 或更高,甚至 N * (1 + 平均等待时间 / 平均计算时间)
- 可通过压测调整。
3. 队列选择
- 优先使用有界队列(如 ArrayBlockingQueue),避免内存溢出。
- 队列大小需结合业务峰值、响应时间、系统资源综合评估。
4. 拒绝策略
- 生产环境推荐:
- CallerRunsPolicy:降级处理,避免丢任务。
- 自定义策略:记录日志、发送告警、持久化任务等。
5. 监控与可观测性
- 自定义 ThreadFactory 设置有意义的线程名(如 "order-task-pool-thread-%d")。
- 通过 ThreadPoolExecutor 的 getActiveCount()、getQueue().size() 等方法监控状态。
- 集成 Micrometer / Prometheus 暴露指标。
五、实战示例
示例:创建一个安全的 IO 密集型线程池
import java.util.concurrent.*;
public class SafeThreadPoolExample {
public static void main(String[] args) {
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
int maxPoolSize = corePoolSize * 2;
int queueCapacity = 100;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "my-pool-thread-" + threadNumber.getAndIncrement());
t.setDaemon(false);
return t;
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志、告警、降级处理
System.err.println("Task rejected: " + r.toString());
// 可选:尝试调用者线程执行
if (!executor.isShutdown()) {
r.run();
}
}
}
);
// 使用
for (int i = 0; i < 200; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟 IO
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}
六、常见误区与避坑指南
- 直接使用 Executors.newFixedThreadPool()
→ 队列无界,高负载下可能 OOM。 - 线程池未关闭
→ 导致 JVM 无法退出(尤其非守护线程)。 - 忽略异常处理
→ 任务中未捕获异常会导致线程静默退出。建议在任务外层加 try-catch。 - 共享线程池不当
→ 不同业务共用一个池,相互影响。建议按业务隔离。 - 未监控线程池状态
→ 无法及时发现队列堆积、拒绝任务等问题。
七、总结
要点 | 建议 |
创建方式 | 手动 new ThreadPoolExecutor |
队列 | 使用有界队列(如 ArrayBlockingQueue) |
线程数 | CPU 密集型 ≈ N+1;IO 密集型 ≈ 2N 或更高 |
拒绝策略 | 自定义或 CallerRunsPolicy |
监控 | 自定义线程名 + 暴露指标 + 日志告警 |
结合业务场景、基于压测数据和监控反馈持续优化线程池配置,是保障高并发系统稳定性的关键。