在 Java 并发编程体系中,线程池是高并发服务的核心基石。从底层的线程资源复用,到上层的业务吞吐量控制,线程池的设计合理性与配置精准度,直接决定了系统的性能上限与稳定性边界。很多开发者对线程池的理解停留在「七大参数」的表层记忆,却对其底层实现原理、生产环境调优、线上故障排查缺乏体系化的认知。
本文将从 ThreadPoolExecutor 的核心 API 出发,逐层拆解 addWorker、Worker 类、runWorker 等核心源码,再延伸到生产环境的参数选型、动态调参、线程池隔离、故障排查实战,最后整理大厂高频面试题,帮你建立从原理到落地的完整线程池知识体系。
一、线程池核心基础:核心 API 与核心概念
线程池的本质是「池化思想」在并发场景的落地:通过复用已创建的线程,减少线程创建与销毁的系统开销,同时通过控制最大并发线程数,避免资源耗尽引发的系统雪崩。
1.1 构造函数:七大核心参数
ThreadPoolExecutor 是 JDK 线程池的标准实现,其完整构造函数包含 7 个核心参数,每一个都直接影响线程池的运行行为:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
| 参数 | 核心含义 | 作用说明 |
|---|---|---|
| corePoolSize | 核心线程数 | 线程池长期保留的线程数量,默认空闲时也不会回收 |
| maximumPoolSize | 最大线程数 | 线程池允许创建的最大线程数,队列满时触发非核心线程创建 |
| keepAliveTime | 空闲存活时间 | 非核心线程空闲超过该时间会被回收;核心线程默认不回收 |
| unit | 时间单位 | keepAliveTime 的时间单位,如秒、毫秒 |
| workQueue | 任务队列 | 存放待执行任务的阻塞队列,是线程池「削峰填谷」的核心载体 |
| threadFactory | 线程工厂 | 用于创建工作线程,可自定义线程名、优先级、守护线程属性 |
| handler | 拒绝策略 | 线程池达到承载上限时,对新任务的处理策略 |
JDK 内置了 4 种标准拒绝策略:
AbortPolicy:默认策略,直接抛出RejectedExecutionException异常CallerRunsPolicy:由提交任务的线程同步执行,实现天然背压DiscardPolicy:直接静默丢弃任务,不抛出异常DiscardOldestPolicy:丢弃队列中最老的任务,重新尝试提交当前任务
1.2 任务提交:execute 与 submit
线程池提供了两种任务提交方式,对应不同的业务场景:
execute 方法 :定义在 Executor 接口中,仅支持提交 Runnable 任务,无返回值,也无法捕获任务执行过程中抛出的异常,适用于异步无结果的场景。
arduino
void execute(Runnable command);
submit 方法 :定义在 ExecutorService 接口中,支持提交 Runnable 和 Callable 任务,返回 Future 对象。通过 Future 可以获取任务返回值、取消任务、阻塞等待结果,也能捕获执行异常。
scss
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);
二者的底层关联:submit 内部会将任务包装为 FutureTask(同时实现了 Runnable 和 Future 接口),最终还是调用 execute 方法执行。
1.3 优雅关闭:shutdown 与 shutdownNow
线程池的关闭有两种不同强度的模式,对应不同的停机场景:
- shutdown() :调用后线程池进入
SHUTDOWN状态,不再接受新任务,但会继续执行队列中已有的任务,所有任务执行完毕后进入终止状态,属于「温和关闭」,保证已提交任务不丢失。 - shutdownNow() :调用后线程池进入
STOP状态,立即停止接收新任务,尝试中断正在执行的任务,并返回队列中未执行的任务列表,属于「强制关闭」,速度快但存在任务丢失风险。
生产环境标准关闭范式:
scss
executor.shutdown();
// 等待指定时间,若未终止则强制关闭
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
二、源码深度解析:线程池工作原理与核心实现
读懂线程池源码,是从「会用」到「精通」的必经之路。Doug Lea 在 ThreadPoolExecutor 中运用了大量并发优化技巧,每一处设计都有其考量。
2.1 任务提交流程总览
当向线程池提交一个任务时,执行逻辑遵循严格的优先级顺序:
- 核心线程阶段 :若当前工作线程数 <
corePoolSize,创建新的核心线程执行任务 - 入队阶段:核心线程已满,尝试将任务加入阻塞队列
- 非核心线程阶段 :队列已满且当前线程数 <
maximumPoolSize,创建非核心线程执行任务 - 拒绝阶段:达到最大线程数且队列已满,执行拒绝策略
2.2 execute 方法源码与设计细节
scss
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get();
// 1. 工作线程数小于核心线程数,直接创建新线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 线程池处于运行状态,尝试入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 双重检查:入队后再次校验线程池状态
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 入队失败,尝试创建非核心线程
else if (!addWorker(command, false))
reject(command);
}
这里有两个经典设计:
- ctl 变量 :一个
AtomicInteger变量,高 3 位存储线程池状态,低 29 位存储工作线程数量,通过一个变量同时维护两个维度的信息,大幅减少了锁的使用。 - 双重状态校验:任务入队后再次校验线程池状态,避免入队瞬间线程池被关闭导致任务永久滞留队列。
2.3 addWorker:线程创建的核心逻辑
addWorker 是线程池创建工作线程的核心方法,分为「CAS 增加线程计数」和「创建启动 Worker 线程」两个阶段:
ini
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 状态校验:线程池已关闭则不创建新线程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS自旋增加工作线程计数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
workers.add(w); // 加入工作线程集合
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动工作线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
核心设计思想:高频的计数操作通过 CAS 自旋无锁完成,低频的集合操作通过加锁保证安全,锁细化的设计大幅提升了高并发下的提交性能。
2.4 Worker 类:为什么要继承 AQS
Worker 是线程池的工作执行单元,每个 Worker 对应一个工作线程。它同时继承了 AbstractQueuedSynchronizer 并实现了 Runnable 接口,身兼「任务执行载体」和「状态同步锁」双重角色。
arduino
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // 初始状态为-1,禁止线程启动前被中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
// 不可重入的独占锁实现
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
Worker 不直接使用 ReentrantLock,而是自己实现 AQS,有三个核心原因:
- 状态与工作绑定 :
state=0表示线程空闲,state=1表示正在执行任务。锁的持有状态和线程工作状态完全绑定,线程池关闭时可以精准识别并中断空闲线程,不会打断正在执行的任务。 - 不可重入特性:可重入锁可能在任务执行过程中因重入释放锁,导致 state 临时变为 0,被误判为空闲线程而中断。不可重入设计保证了「持有锁 = 正在工作」的语义绝对成立。
- 轻量化设计 :Worker 只需要最简单的独占锁语义,不需要公平锁、条件变量等高级特性,自主实现 AQS 内存开销更小。
而构造函数中 setState(-1) 是一个精妙的边界处理:线程池的中断方法会判断 state >= 0 才执行中断,初始状态 - 1 保证了线程创建后、启动前不会被意外中断,直到 runWorker 中调用 unlock() 将 state 置为 0,线程才正式进入可中断状态。
2.5 runWorker:线程复用的核心循环
runWorker 是工作线程的主生命周期,也是线程池「线程复用」的本质 ------ 一个线程在循环中反复从队列取任务执行,直到取不到任务才退出。
ini
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // state从-1置为0,允许被中断
boolean completedAbruptly = true;
try {
// 核心循环:有任务执行,无任务从队列拉取
while (task != null || (task = getTask()) != null) {
w.lock(); // 加锁,标记线程正在工作
// 保证STOP状态下线程一定持有中断标记
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); // 执行前钩子
Throwable thrown = null;
try {
task.run(); // 真正执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown); // 执行后钩子
}
} finally {
task = null;
w.completedTasks++;
w.unlock(); // 解锁,标记线程空闲
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); // 线程退出清理
}
}
两个关键细节:
- 加锁的目的不是保护任务线程安全,而是标记工作状态,供线程池关闭时识别空闲线程。
beforeExecute和afterExecute是空实现的钩子方法,是生产环境实现监控、异常捕获的核心扩展点。
2.6 getTask:线程回收的决策逻辑
getTask 决定了工作线程何时退出,是核心线程与非核心线程差异化回收策略的实现位置:
ini
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 关闭状态校验:满足条件则返回null,线程退出
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 判断当前线程是否需要超时回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 超时回收线程用poll限时等待,核心线程用take永久阻塞
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
核心逻辑:通过 timed 变量区分线程类型,需要回收的线程使用 poll 超时等待,超时未取到任务则退出;核心线程默认使用 take 永久阻塞,持续等待新任务。
三、生产环境配置:参数选型与监控体系
线程池参数没有万能公式,需要结合业务场景压测调优,但有成熟的选型原则可遵循。
3.1 线程数大小的选型公式
- CPU 密集型任务 :推荐
corePoolSize = CPU核心数 + 1CPU 密集型任务 CPU 利用率高,过多线程只会增加上下文切换开销;+1 是为了应对线程偶发缺页中断,保证 CPU 利用率最大化。 - IO 密集型任务 :推荐
corePoolSize = 2 * CPU核心数IO 密集型任务线程大部分时间处于阻塞等待状态,更多线程可以在等待时切换执行其他任务,提升吞吐量。更精准的公式为:线程数 = CPU核心数 * (1 + 平均等待时间 / 平均计算时间)。
3.2 阻塞队列的选型对比
| 队列类型 | 核心特点 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界数组队列,FIFO,容量固定 | 流量可控的稳定业务,防止任务无限堆积 |
| LinkedBlockingQueue | 链表队列,默认容量为 Integer.MAX_VALUE | 任务量平稳的场景;无界配置存在 OOM 风险 |
| SynchronousQueue | 不存储任务,直接交付给线程执行 | 低延迟场景,配合较大的最大线程数使用 |
| PriorityBlockingQueue | 优先级无界队列 | 需要按优先级执行任务的特殊场景 |
3.3 拒绝策略的选型建议
- 核心业务、不可丢失的任务:选择
CallerRunsPolicy,通过提交方同步执行实现背压,既不丢失任务,又能降低提交速率。 - 非核心、可丢弃的任务(如日志上报、统计):选择
DiscardPolicy,队列满时直接丢弃,不影响主链路。 - 严格保证不丢失、需要感知失败的场景:选择
AbortPolicy,抛出异常由业务方做降级处理。
3.4 线程池监控指标与扩展钩子
线上运行的线程池必须具备完善的可观测性,ThreadPoolExecutor 提供了丰富的原生监控指标:
getTaskCount():已提交的任务总数getCompletedTaskCount():已完成的任务数getLargestPoolSize():历史达到的最大线程数getPoolSize():当前工作线程总数getActiveCount():当前正在执行任务的活跃线程数
通过继承 ThreadPoolExecutor 重写 beforeExecute、afterExecute、terminated 三个钩子方法,可以实现任务耗时统计、异常捕获、监控上报等定制化能力。生产级可监控线程池实现示例:
scala
public class MonitorableThreadPool extends ThreadPoolExecutor {
private final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
private final AtomicLong failTaskCount = new AtomicLong(0);
private final AtomicLong totalCostTime = new AtomicLong(0);
private final String poolName;
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startTimeThreadLocal.set(System.currentTimeMillis());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
long cost = System.currentTimeMillis() - startTimeThreadLocal.get();
startTimeThreadLocal.remove();
totalCostTime.addAndGet(cost);
if (t != null) {
failTaskCount.incrementAndGet();
// 记录异常日志、上报告警
}
}
}
四、线上优化实战:动态调参与性能优化
4.1 线上常见问题与根因
- 核心线程不足,队列等待过长 现象:接口响应慢,活跃线程数长期等于核心线程数,队列积压严重。根因是核心线程数设置过于保守,流量高峰时任务全部排队,等待时间远超执行时间。
- 队列过短,频繁触发拒绝 现象:业务高峰期大量抛出拒绝异常。根因是队列容量设置过小,流量突增时快速打满,直接触发拒绝策略。
- 异常导致线程频繁重建 现象:线程数频繁波动,GC 次数偏多。根因是 execute 提交的任务未捕获异常,异常会导致 Worker 线程直接死亡,线程池频繁创建新线程补充。
4.2 线程池动态参数调整实现
ThreadPoolExecutor 原生支持运行时动态修改核心参数,无需重启服务即可应对流量波动,是生产环境弹性调优的基础能力。
arduino
public void refreshConfig(int newCoreSize, int newMaxSize, long newKeepAliveTime, TimeUnit unit) {
// 先改最大线程数,再改核心线程数,避免core > max触发异常
if (newMaxSize >= newCoreSize) {
executor.setMaximumPoolSize(newMaxSize);
executor.setCorePoolSize(newCoreSize);
executor.setKeepAliveTime(newKeepAliveTime, unit);
}
}
生产环境通常对接配置中心(Nacos/Apollo)实现参数热更新,大促前提前扩容,低谷期自动缩容,兼顾性能与资源成本。
4.3 经典优化方案:SynchronousQueue + 调用者执行策略
对于短耗时、低延迟要求的场景,推荐如下配置范式:
scss
ThreadPoolExecutor fastTaskPool = new ThreadPoolExecutor(
0,
Runtime.getRuntime().availableProcessors() * 3,
30L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new NamedThreadFactory("fast-task-pool"),
new ThreadPoolExecutor.CallerRunsPolicy()
);
设计思路:
SynchronousQueue不缓存任务,任务提交后直接交给线程执行,避免队列等待延迟- 核心线程为 0,闲时零资源占用,线程全部可回收
CallerRunsPolicy拒绝策略实现天然背压:任务处理不过来时,提交线程自己执行,自动降低提交速率,既不丢失任务,又能起到流量削峰的效果
五、Executors 框架:便捷背后的风险
5.1 Executor 框架整体结构
java.util.concurrent.Executors 是 JDK 提供的线程池工厂工具类,整个 Executor 框架分为两大组成部分:
- 工作单元 :
Runnable和Callable,代表待执行的任务实体 - 执行机制 :以
Executor接口为核心的执行体系,核心实现为ThreadPoolExecutor和ScheduledThreadPoolExecutor
5.2 Runnable 与 Callable 的区别与适用场景
| 对比维度 | Runnable | Callable |
|---|---|---|
| 返回值 | 无返回值 | 支持泛型返回值 |
| 异常处理 | 无法向上抛出受检异常 | 可抛出受检异常,通过 Future.get () 获取 |
| 提交方式 | execute、submit 均可 | 只能通过 submit 提交 |
| 适用场景 | 异步无结果任务,如日志上报、消息发送 | 需要执行结果的计算任务、多任务聚合 |
5.3 为什么不推荐使用 Executors 创建线程池
《阿里巴巴 Java 开发手册》明确禁止使用 Executors 创建线程池,核心原因在于其内置实现存在资源耗尽风险:
- FixedThreadPool / SingleThreadPool :使用无界
LinkedBlockingQueue,默认容量为Integer.MAX_VALUE,任务堆积时极易引发 OOM。 - CachedThreadPool :最大线程数设置为
Integer.MAX_VALUE,高并发下会创建大量线程,耗尽系统 CPU 和内存资源。 - ScheduledThreadPool:同样使用无界队列,任务堆积风险高,且异常任务会导致调度终止。
- 默认线程工厂:生成的线程名无业务含义,线上排查问题难以定位归属。
生产环境最佳实践:通过 ThreadPoolExecutor 构造函数手动创建,明确指定每一个参数,自定义线程工厂设置业务化线程名,根据业务场景选择合适的拒绝策略。
六、线程池隔离:舱壁模式的工程落地
6.1 为什么必须做线程池隔离
如果所有业务共用一个全局线程池,某一个慢任务或故障业务占满线程池时,会导致所有业务全部排队等待,形成级联雪崩。比如短信服务商故障导致短信任务阻塞,最终拖垮整个订单链路,这就是典型的「一损俱损」。
线程池隔离本质是舱壁模式(Bulkhead Pattern) 的落地:将系统资源拆分成多个独立的池,故障影响被限制在自身范围内,保障核心业务的资源配额与可用性。
6.2 三个核心隔离维度
生产环境通常按多个维度组合拆分,核心原则是:风险不同、优先级不同、特性不同的任务,不共用线程池。
- 按业务优先级隔离:核心业务池配额充足、策略保守;非核心业务池资源有限,高峰期可降级丢弃。
- 按任务耗时隔离:快任务与慢任务拆分,避免慢任务长时间占用线程,导致快任务排队。
- 按业务域隔离:按业务领域拆分独立线程池,故障时可快速定位业务线,避免跨业务影响。
6.3 生产级线程池管理器实现
散落在业务代码中创建线程池会导致资源不可控、监控不统一。生产环境推荐通过统一管理器注册和管理所有线程池,实现「统一创建、统一监控、统一销毁」。
arduino
public class ThreadPoolManager {
private final Map<String, MonitorableThreadPool> poolMap = new ConcurrentHashMap<>();
private static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
public void registerPool(String poolName, int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
// 创建并注册可监控线程池
}
public MonitorableThreadPool getPool(String poolName) {
return poolMap.get(poolName);
}
public void reportAllMetrics() {
// 批量上报所有线程池监控指标
}
public void shutdownAll() {
// 服务停机时优雅关闭所有线程池
}
}
七、线上故障排查:方法论与经典案例
7.1 通用排查四步法与工具清单
遇到线程池相关问题,遵循「看监控 → 抓线程栈 → 定位异常任务 → 验证修复」的四步排查法:
- 看监控指标:判断是线程打满、队列积压、拒绝突增还是耗时上涨,快速定位问题类型。
- 抓线程栈:通过线程栈查看线程真实状态,定位是锁等待、IO 阻塞还是死循环。
- 定位异常任务:结合业务日志,定位是哪一类任务导致的问题,是否有下游故障。
- 验证修复:调整参数或修复问题后,持续观察监控指标确认恢复。
常用工具:Arthas(线上排查首选)、jstack、jmap + MAT、监控平台。
7.2 四大经典线上故障复盘
案例 1:核心线程不足 + 队列过长,接口全线超时
现象:大促高峰期接口响应从 100ms 飙升到 2s,大量超时,CPU 利用率仅 30%。
根因:核心线程数设置过于保守,任务处理能力跟不上流量,全部在队列排队,等待时间远超执行时间。
修复:调大核心线程数,缩短队列长度,超过容量直接降级而非长时间等待。
案例 2:无界队列引发 OOM,服务频繁挂掉
现象:低峰期服务频繁 Full GC,最终 OOM 重启。
根因 :使用 Executors.newFixedThreadPool 创建的线程池,队列无界;下游日志服务故障时任务只进不出,最终撑爆内存。
修复:替换为有界队列,非核心任务使用丢弃策略,增加队列积压告警。
案例 3:未做隔离,慢任务拖垮全服务
现象:所有接口响应变慢,吞吐量下降 80%,但 CPU 内存不高。
根因:公共线程池被第三方慢调用占满,核心业务任务全部排队。
修复:按业务优先级和耗时拆分线程池,第三方调用独立池并配置熔断。
案例 4:未捕获异常,线程频繁销毁重建
现象:线程数频繁波动,GC 次数偏多,任务量平稳但线程创建销毁频繁。
根因:execute 提交的任务未捕获异常,异常导致 Worker 线程死亡,线程池反复创建新线程。
修复:任务内层增加异常捕获,重写 afterExecute 统一处理异常。
7.3 排查常用命令速查
ini
# Arthas查看线程状态
thread
# jstack抓取线程栈
jstack <pid> > thread_dump.txt
# 查看进程线程总数
ps -T -p <pid> | wc -l
# 导出堆快照分析OOM
jmap -dump:format=b,file=heap_dump.hprof <pid>
八、大厂高频面试题汇总
基础原理篇
- 线程池的七大核心参数是什么?分别有什么作用?
- 任务提交后线程池的执行流程是什么?
- shutdown 和 shutdownNow 有什么区别?
- Runnable 和 Callable 有什么区别?Future 和 FutureTask 是什么关系?
- 四种内置拒绝策略分别是什么?适用场景有哪些?
源码深度篇
- Worker 类为什么要继承 AQS?为什么不直接用 ReentrantLock?
- addWorker 方法的执行流程是什么?为什么用 CAS 自旋而不是直接加锁?
- runWorker 方法的核心逻辑是什么?beforeExecute 和 afterExecute 有什么作用?
- ctl 变量的设计有什么巧妙之处?
- 任务执行抛出异常后,线程池会怎么处理?
- 核心线程默认不会被回收,如何让核心线程也超时回收?
生产实战篇
- 如何设置线程池大小?CPU 密集型和 IO 密集型分别怎么配置?
- 为什么不建议使用 Executors 创建线程池?具体有什么风险?
- 线上线程池如何监控?重点关注哪些指标?
- 生产环境遇到线程池队列积压怎么办?如何排查和优化?
- 线上可以动态修改线程池参数吗?如何实现?
进阶拓展篇
- 线程池会导致死锁吗?什么场景下会出现?
- 什么是线程池隔离?为什么要做线程池隔离?
- 如何优雅地关闭线程池?标准范式是什么?
- 核心线程数 = CPU 核心数 + 1 这个公式绝对正确吗?为什么?