一、为什么要使用线程池?
不使用线程池直接创建线程会带来严重问题:
- 资源开销大:频繁创建/销毁线程消耗大量CPU和内存资源,线程创建本身是重量级操作
- 系统不稳定 :无限制创建线程可能导致:
- 内存溢出(OOM):每个线程需分配栈空间(默认1MB)
- 上下文切换频繁:线程过多导致CPU在切换上消耗过多资源
- 系统崩溃:耗尽操作系统线程资源
- 线程池的核心价值 :
- 复用线程:避免重复创建销毁的开销
- 控制并发:限制最大线程数,保护系统资源
- 任务排队:通过队列平滑处理突发流量
- 统一管理 :监控、统计、优雅关闭等能力
二、为什么不推荐使用Executors内置线程池?
阿里巴巴Java开发手册明确强制规定 :线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor方式。原因如下:
| Executors方法 | 风险点 | 具体问题 |
|---|---|---|
newFixedThreadPool / newSingleThreadExecutor |
队列无界 | 内部使用LinkedBlockingQueue(默认容量Integer.MAX_VALUE),任务堆积会导致OOM 博客园 |
newCachedThreadPool |
线程数无界 | 最大线程数为Integer.MAX_VALUE,高并发下可能创建海量线程导致系统崩溃 知乎 |
newScheduledThreadPool |
队列无界 | 同样使用无界队列,存在内存溢出风险 |
核心问题 :Executors封装过度,隐藏了关键参数(如队列容量、拒绝策略),开发者无法感知资源风险,容易在生产环境引发事故
✅ 正确做法 :显式使用ThreadPoolExecutor构造函数,明确指定所有参数:
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<>(100), // 有界队列!
new CustomThreadFactory(), // 可自定义线程名
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
三、线程池核心参数(ThreadPoolExecutor 7个参数)
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory,// 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
| 参数 | 说明 | 最佳实践 |
|---|---|---|
| corePoolSize | 核心线程数,即使空闲也会保留 | 根据CPU核心数和任务类型设置: • CPU密集型:N+1(N为CPU核心数) • IO密集型:2N或更高 阿里云官方网站 |
| maximumPoolSize | 最大线程数,队列满后可扩容至此值 | 需结合队列容量设置,避免无界增长 |
| keepAliveTime | 非核心线程空闲存活时间 | 通常设为30-60秒,平衡资源回收与创建开销 |
| workQueue | 任务队列 | 必须使用有界队列 (如ArrayBlockingQueue),避免OOM 知乎 |
| threadFactory | 线程工厂 | 自定义线程名(如"order-task-pool-%d"),便于排查问题 |
| handler | 拒绝策略 | 根据业务场景选择(见下文) |
执行流程:
- 当前运行线程数 <
corePoolSize→ 创建新核心线程执行 - 达到
corePoolSize→ 任务入队workQueue - 队列满且线程数 <
maximumPoolSize→ 创建非核心线程 - 队列满且达到
maximumPoolSize→ 触发拒绝策略
四、线程池拒绝策略(4种内置 + 自定义)
当线程数达到maximumPoolSize + 队列已满时触发拒绝策略:
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException异常 |
通用场景,快速失败便于发现问题 腾讯 |
| CallerRunsPolicy | 由提交任务的线程(调用者)直接执行 | 降低提交速度,适用于允许降级的场景 |
| DiscardPolicy | 静默丢弃任务,不抛异常 | 允许任务丢失的场景(如日志上报) |
| DiscardOldestPolicy | 丢弃队列中最老的任务,尝试重新提交新任务 | 保留最新任务的场景(如实时数据处理) |
自定义拒绝策略示例:
java
public class CustomRejectPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1. 记录日志告警
log.warn("Task rejected, queue size: {}", executor.getQueue().size());
// 2. 可选:持久化到DB/消息队列,后续重试
// taskPersistence.save(r);
// 3. 可选:降级处理
// fallbackService.handle(r);
throw new RejectedExecutionException("Task rejected due to overload");
}
}
五、生产环境最佳实践
-
必须使用有界队列 :
new ArrayBlockingQueue<>(100),避免OOM -
监控关键指标 :
javaexecutor.getActiveCount(); // 活跃线程数 executor.getQueue().size(); // 队列堆积量 executor.getCompletedTaskCount();// 完成任务数 -
优雅关闭 :
javaexecutor.shutdown(); // 停止接收新任务,等待已有任务完成 // 或 executor.shutdownNow(); // 立即中断所有任务 -
命名规范 :通过
ThreadFactory设置有意义的线程名,便于排查问题 -
参数调优 :根据压测结果动态调整
corePoolSize/queueCapacity知乎
💡 总结 :线程池是双刃剑------用得好提升系统吞吐量,用不好直接导致生产事故。务必显式创建
ThreadPoolExecutor,明确所有参数,尤其是队列必须有界,并根据业务选择合适的拒绝策略。
一、线程池处理任务的完整流程
ThreadPoolExecutor 的任务处理遵循以下四步决策链:
关键细节:
- 核心线程默认不会超时回收 (除非调用
allowCoreThreadTimeOut(true)) - 非核心线程在空闲超过
keepAliveTime后会被回收 - 队列类型影响行为:
SynchronousQueue:不存储任务,必须立即有空闲线程,否则创建新线程(适合CachedThreadPool)LinkedBlockingQueue/ArrayBlockingQueue:先入队,队列满才扩容线程
二、线程异常后:销毁还是复用?
✅ 线程会被复用,但需正确处理异常,否则可能"静默死亡"导致线程池失效。
问题场景(错误写法):
java
executor.execute(() -> {
throw new RuntimeException("任务异常"); // 未捕获 → 线程终止,任务丢失
});
此线程会因未捕获异常而退出,线程池会创建新线程替代,但任务已丢失且无日志,极难排查。
正确做法(3种方案):
方案1:任务内部 try-catch(推荐)
java
executor.execute(() -> {
try {
// 业务逻辑
process();
} catch (Exception e) {
log.error("任务执行异常", e);
// 可选:告警、降级、重试
}
});
方案2:自定义 ThreadFactory 捕获未处理异常
java
ThreadFactory namedFactory = r -> {
Thread t = new Thread(r, "biz-task-" + seq.incrementAndGet());
t.setUncaughtExceptionHandler((thread, ex) -> {
log.error("线程[{}]未捕获异常", thread.getName(), ex);
monitor.alert("ThreadPoolUncaughtException", ex);
});
return t;
};
方案3:继承 ThreadPoolExecutor 重写 afterExecute
java
public class SafeThreadPool extends ThreadPoolExecutor {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
((Future<?>) r).get(); // 获取异步任务异常
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
log.error("任务执行异常", t);
}
}
}
✅ 结论:线程异常不会自动销毁整个线程池,但未捕获异常会导致该线程退出,线程池会创建新线程补充。务必通过上述方式捕获异常,避免任务静默失败。
三、如何给线程命名?
使用 ThreadFactory 自定义线程名,便于日志追踪和问题定位:
java
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger seq = new AtomicInteger(1);
private final String namePrefix;
private final boolean daemon;
public NamedThreadFactory(String namePrefix, boolean daemon) {
this.namePrefix = namePrefix;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + "-" + seq.getAndIncrement());
t.setDaemon(daemon);
t.setUncaughtExceptionHandler((thread, ex) ->
log.error("线程[{}]异常退出", thread.getName(), ex)
);
return t;
}
}
// 使用
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new NamedThreadFactory("order-service", false),
new ThreadPoolExecutor.CallerRunsPolicy()
);
✅ 命名规范建议 :{业务模块}-{功能}-{序号},如 pay-async-01、user-query-03
四、如何动态修改线程池参数?
ThreadPoolExecutor 提供线程安全的 setter 方法,支持运行时调参:
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
// 动态调整核心线程数
executor.setCorePoolSize(8);
// 动态调整最大线程数
executor.setMaximumPoolSize(20);
// 动态调整空闲超时时间
executor.setKeepAliveTime(30L, TimeUnit.SECONDS);
// 允许核心线程超时回收(默认false)
executor.allowCoreThreadTimeOut(true);
⚠️ 注意事项:
| 参数 | 调整方向 | 影响 |
|---|---|---|
corePoolSize 增大 |
立即生效 | 可能立即创建新线程 |
corePoolSize 减小 |
延迟生效 | 现有核心线程不会立即销毁,需等空闲超时 |
maximumPoolSize |
立即生效 | 影响后续扩容上限 |
keepAliveTime |
立即生效 | 影响后续空闲线程回收 |
生产级动态调参方案(结合配置中心):
java
@Component
public class DynamicThreadPool {
private final ThreadPoolExecutor executor;
public DynamicThreadPool() {
this.executor = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new NamedThreadFactory("dynamic-pool", false),
new ThreadPoolExecutor.AbortPolicy());
}
// 配置中心回调
public void updateConfig(int coreSize, int maxSize, int queueCap) {
executor.setCorePoolSize(coreSize);
executor.setMaximumPoolSize(maxSize);
// 队列容量无法动态修改,需替换(不推荐生产使用)
// 建议:重启应用或使用多级队列设计
}
// 暴露监控指标
public Map<String, Object> getMetrics() {
return Map.of(
"activeCount", executor.getActiveCount(),
"queueSize", executor.getQueue().size(),
"completedTasks", executor.getCompletedTaskCount(),
"corePoolSize", executor.getCorePoolSize(),
"maximumPoolSize", executor.getMaximumPoolSize()
);
}
}
💡 建议:通过 Apollo/Nacos 监听配置变更,结合 Metrics(如 Micrometer)监控队列堆积,实现自动扩缩容。
五、如何设计优先级线程池?
使用 PriorityBlockingQueue + 任务实现 Comparable 接口:
方案1:任务实现 Comparable(推荐)
java
@Data
@AllArgsConstructor
public class PriorityTask implements Runnable, Comparable<PriorityTask> {
private final int priority; // 值越小优先级越高
private final Runnable delegate;
private final long submitTime = System.nanoTime(); // 提交时间,用于同优先级排序
@Override
public void run() {
delegate.run();
}
@Override
public int compareTo(PriorityTask o) {
if (this.priority != o.priority) {
return Integer.compare(this.priority, o.priority); // 升序:小优先
}
return Long.compare(this.submitTime, o.submitTime); // 先提交先执行
}
}
// 创建优先级线程池
ThreadPoolExecutor priorityExecutor = new ThreadPoolExecutor(
3, 10, 60L, TimeUnit.SECONDS,
new PriorityBlockingQueue<>(100), // 无界队列,注意OOM风险
new NamedThreadFactory("priority-pool", false),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 提交任务
priorityExecutor.execute(new PriorityTask(1, () -> System.out.println("高优先级")));
priorityExecutor.execute(new PriorityTask(5, () -> System.out.println("低优先级")));
priorityExecutor.execute(new PriorityTask(1, () -> System.out.println("同优先级-后提交")));
方案2:自定义 Comparator(更灵活)
java
BlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(
100,
Comparator.comparingInt(r -> ((PriorityTask) r).getPriority())
);
// 注意:需强制类型转换,不如方案1类型安全
⚠️ 优先级线程池注意事项:
-
队列必须可比较 :所有任务必须实现
Comparable或提供Comparator -
避免无界队列 :
PriorityBlockingQueue默认无界,建议封装有界版本:javapublic class BoundedPriorityQueue<E> extends PriorityBlockingQueue<E> { private final int capacity; public BoundedPriorityQueue(int capacity) { this.capacity = capacity; } @Override public boolean offer(E e) { return size() < capacity && super.offer(e); } } -
优先级设计原则 :
- 避免过多优先级级别(建议 3~5 级)
- 防止低优先级任务"饥饿",可定期提升等待过久任务的优先级
-
适用场景:订单支付 > 普通查询、风控拦截 > 日志上报等
总结与最佳实践
| 问题 | 核心要点 |
|---|---|
| 执行流程 | 核心线程 → 队列 → 非核心线程 → 拒绝策略,四步决策 |
| 异常处理 | 线程会复用,但未捕获异常会导致线程退出,务必全局捕获 |
| 线程命名 | 通过 ThreadFactory 命名,格式:{模块}-{功能}-{序号} |
| 动态调参 | 使用 setCorePoolSize() 等 API,结合配置中心实现弹性伸缩 |
| 优先级调度 | PriorityBlockingQueue + Comparable,注意队列有界性与饥饿问题 |
💡 终极建议 :生产环境推荐使用 美团的 DynamicTp 或 京东的 Schrodinger 等开源线程池治理框架,提供动态调参、监控告警、任务追踪等企业级能力,避免重复造轮子。