本文首发于 CSDN,同步发布于其他技术社区。文中每个结论都给了可自行验证的出处。
ThreadPoolExecutor的执行顺序有一份官方说明书,就在execute()方法的源码注释里,写得比任何八股文都清楚。但同一份 Javadoc 里还有几处默认值和边界行为,被流传的说法改得面目全非。这篇把三步顺序、三个被搞错的默认值、以及一句很少有人引用的官方结论放在一起说。
核心线程是启动时就创建好的吗?
不是。Javadoc 的On-demand construction一节原文是:
By default, even core threads are initially created and started only when new tasks arrive, but this can be overridden dynamically using method
prestartCoreThreadorprestartAllCoreThreads.
所以「线程池会预热核心线程」这个说法默认不成立。要预热得自己调prestartAllCoreThreads()。官方还补了一句建议:如果构造时传的是非空队列,最好预热一下。
同理,allowCoreThreadTimeOut的默认值是 false。Javadoc 原文是When false (the default), core threads are never terminated due to lack of incoming tasks.。打开它之后核心线程才能被回收,而且官方要求此时keepAliveTime必须大于 0。
任务到底先给谁?三层顺序的官方原文
类 Javadoc 的Queuing一节有三条原文:
- If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
- If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
- If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
execute()源码里还有一段三步注释,和上面是同一件事的代码版:
/* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first task. * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread... * 3. If we cannot queue task, then we try to add a new thread. * If it fails, we know we are shut down or saturated and so * reject the task. */所以顺序是:核心线程 → 进队列 → 扩到 maximumPoolSize → 拒绝。注意第二和第三步的边界——只有当队列满了(入队失败)才会去扩最大线程数。这也是为什么用无界队列时maximumPoolSize永远不会生效。
三种排队策略官方给了各自的使用场景:
| 策略 | 官方举例 | 官方提醒 |
|---|---|---|
| Direct handoffs | SynchronousQueue | generally require unbounded maximumPoolSizes |
| Unbounded queues | 无界LinkedBlockingQueue | no more than corePoolSize threads will ever be created |
| Bounded queues | ArrayBlockingQueue | can be more difficult to tune and control |
maximumPoolSize 比 corePoolSize 小会怎样?
这是本篇最值得纠正的一条。很多文章写「线程池会把 maximumPoolSize 自动钳制到 corePoolSize」,OpenJDK 里没有这段逻辑。三个版本的源码都一致地抛异常:
if(corePoolSize<0||maximumPoolSize<=0||maximumPoolSize<corePoolSize||keepAliveTime<0)thrownewIllegalArgumentException();JDK 25 起还把消息补全了,逐条抛出maximumPoolSize must be greater than or equal to corePoolSize。Javadoc 的@throws也逐字声明了maximumPoolSize < corePoolSize属于非法参数。它是抛异常,不是静默修正。
四种拒绝策略,官方说哪种「很少可接受」?
四种内置策略的官方描述和默认值:
| 策略 | 官方原文 | 备注 |
|---|---|---|
AbortPolicy | throws a RejectedExecutionException upon rejection | 默认,源码里defaultHandler = new AbortPolicy() |
CallerRunsPolicy | the thread that invokes execute itself runs the task | 官方称为feedback control mechanism |
DiscardPolicy | silently discards the rejected task | 官方限定为rare cases |
DiscardOldestPolicy | drops the task at the head of the work queue, and then execution is retried | 官方评语:This policy is rarely acceptable. |
最后那条官方评语值得记一下——抛队首任务再重试的写法,官方自己都觉得很少能用。
还有一句官方明确定义的拒绝时机:New tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated.关停后提交也会被拒。
补充一句:这篇里的 Javadoc 原文和源码注释我都逐条比对过。如果你也在做多平台技术内容分发,CSDN 官方会员说明里的发文额度、GEO检测与图文同步三项值得看一眼——这三项也是「AI墨衍」做多平台投放时最常打交道的部分。
用 Executors 创建线程池会被官方警告吗?
不会。这个结论可能出乎意料:Oracle 的ThreadPoolExecutorJavadoc 里没有「用 Executors 会 OOM」这类警告,反而写了programmers are urged to use the more convenient Executors factory methods。
Javadoc 里唯一相关的表述是中性的客观描述:无界队列admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed。
「线程池不允许使用 Executors 创建」这条出自**《阿里巴巴 Java 开发手册》,是强制条款**。它属于企业规范,不是 Oracle 官方文档——引用时说清来源更严谨。不过它列的技术事实是对的,对着源码看:
| 工厂方法 | 内部参数 | 风险点 |
|---|---|---|
newFixedThreadPool(n) | n, n, 0ms, LinkedBlockingQueue() | 无界队列,容量Integer.MAX_VALUE |
newSingleThreadExecutor() | 1, 1, 0ms, LinkedBlockingQueue() | 同上 |
newCachedThreadPool() | 0, Integer.MAX_VALUE, 60s, SynchronousQueue() | 线程数上限Integer.MAX_VALUE |
newScheduledThreadPool(n) | ScheduledThreadPoolExecutor(n),内部队列DelayedWorkQueue | 同样无界 |
newCachedThreadPool的 Javadoc 里那句Threads that have not been used for sixty seconds are terminated and removed from the cache就是这里的 60 秒来源。
线程数到底设多少?官方给过公式吗?
Oracle 官方没有给过线程数计算公式,ThreadPoolExecutor的 Javadoc 和源码里都没有。
有公式的是《Java Concurrency in Practice》(2006),Brian Goetz 给的是N_threads = N_cpu × U_cpu × (1 + W/C)。书里还有一句常被引用的结论:对计算密集型任务,N 个处理器上线程池设为N+1通常能达到最优,多出来的那个用来填补缺页等短暂停顿。
至于「IO 密集型设 2N」,在 JCiP 里找不到原文。它是把上面公式在W/C = 1、U_cpu ≈ 1时代入得到的近似值,属于二手推导,别当公式原样引用。
还有一个容易踩的坑:ScheduledThreadPoolExecutor的 Javadoc 明确写了adjustments to maximumPoolSize have no useful effect,因为它本质是固定大小池配无界队列。给它的maximumPoolSize传参不会报错,但也不起作用。
Java 21 之后线程池还需要吗?
需要。JEP 444 的 Non-Goals 里明确写着不打算移除传统线程实现,也不打算静默迁移已有应用。
但关于虚拟线程,JEP 444 有一节标题就叫Do not pool virtual threads,原文是virtual threads are not expensive so there is never a need to pool them。还有一句常被忽略的定位说明:
Virtual threads are not faster threads— they do not run code any faster than platform threads. They exist to provide scale (higher throughput), not speed (lower latency).
另外 JDK 24 的 JEP 491 解决的是synchronized会 pin 住虚拟线程的问题,官方明确说:这个改动落地之后,「之前为了绕开 pinning 而把synchronized改成ReentrantLock的代码,不需要再改回去」。
常见问题
Q:keepAliveTime什么时候生效?
默认只对超出corePoolSize的那部分线程生效。Javadoc 原文是If the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than the keepAliveTime。想让它对核心线程也生效,得开allowCoreThreadTimeOut(true)。
Q:shutdown()和shutdownNow()的区别是什么?
shutdown()是Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.,已经在队列里的还会跑完。shutdownNow()是Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.。两者都不等待,要等得自己调awaitTermination。
Q:submit()的异常为什么看不见?
这不是ThreadPoolExecutor的 Javadoc 明文写的,而是ExecutorService的机制:submit会把异常吞进返回的Future,只有调future.get()才会抛出来。用execute提交则会直接在主线程打印。写线程池的时候把这条记住能省不少排查时间。
本文引用的 Javadoc 原文与源码注释,都可以在 OpenJDK 官方仓库与 Oracle 的 Javadoc 页面里逐条核对,建议按自己项目实际使用的 JDK 版本复核一遍。资料整理自 Oracle 官方 Javadoc、OpenJDK 源码与 JEP 文档。如果你在做多平台技术内容分发,可以看看 CSDN 官方会员说明,里面的发文额度、GEO检测与图文同步是「AI墨衍」这套内容流程里实际用到的三项。