文章目录
- 【面试突击】JAVA基础知识-线程池与参数调优
-
- 一、为什么要用线程池?
- [二、`ThreadPoolExecutor` 核心参数详解](#二、
ThreadPoolExecutor核心参数详解) -
- [1. `corePoolSize`:核心线程数](#1.
corePoolSize:核心线程数) - [2. `maximumPoolSize`:最大线程数](#2.
maximumPoolSize:最大线程数) - [3. `keepAliveTime` + `unit`:线程空闲存活时间](#3.
keepAliveTime+unit:线程空闲存活时间) - [4. `workQueue`:任务队列(非常关键)](#4.
workQueue:任务队列(非常关键)) - [5. `threadFactory`:线程工厂](#5.
threadFactory:线程工厂) - [6. `RejectedExecutionHandler`:拒绝策略](#6.
RejectedExecutionHandler:拒绝策略)
- [1. `corePoolSize`:核心线程数](#1.
- [三、队列选择:`ArrayBlockingQueue` vs `LinkedBlockingQueue` vs `SynchronousQueue`](#三、队列选择:
ArrayBlockingQueuevsLinkedBlockingQueuevsSynchronousQueue) -
- [1. `ArrayBlockingQueue`:数组有界阻塞队列](#1.
ArrayBlockingQueue:数组有界阻塞队列) - [2. `LinkedBlockingQueue`:链表阻塞队列(默认近似无界)](#2.
LinkedBlockingQueue:链表阻塞队列(默认近似无界)) - [3. `SynchronousQueue`:零容量队列](#3.
SynchronousQueue:零容量队列)
- [1. `ArrayBlockingQueue`:数组有界阻塞队列](#1.
- 四、拒绝策略:`RejectedExecutionHandler`
-
- [1. `AbortPolicy`(默认)------ 抛异常](#1.
AbortPolicy(默认)—— 抛异常) - [2. `CallerRunsPolicy`------ 由提交线程自己执行](#2.
CallerRunsPolicy—— 由提交线程自己执行) - [3. `DiscardPolicy`------ 直接丢弃当前任务](#3.
DiscardPolicy—— 直接丢弃当前任务) - [4. `DiscardOldestPolicy`------ 丢队头旧任务,保新任务](#4.
DiscardOldestPolicy—— 丢队头旧任务,保新任务)
- [1. `AbortPolicy`(默认)------ 抛异常](#1.
- [五、为什么不推荐直接使用 `Executors` 系列工厂方法?](#五、为什么不推荐直接使用
Executors系列工厂方法?) -
- [1. `newFixedThreadPool` / `newSingleThreadExecutor`](#1.
newFixedThreadPool/newSingleThreadExecutor) - [2. `newCachedThreadPool`](#2.
newCachedThreadPool) - [3. 官方 & 规范建议](#3. 官方 & 规范建议)
- [1. `newFixedThreadPool` / `newSingleThreadExecutor`](#1.
- 六、推荐的线程池创建方式(模板)
- [七、ArrayBlockingQueue 与 LinkedBlockingQueue 的简单对比(面试可用)](#七、ArrayBlockingQueue 与 LinkedBlockingQueue 的简单对比(面试可用))
- 八、面试速记总结
【面试突击】JAVA基础知识-线程池与参数调优
本文聚焦:
ThreadPoolExecutor参数含义、队列选择、拒绝策略,以及为什么不推荐直接用Executors系列工厂方法。
一、为什么要用线程池?
- 线程创建/销毁成本高:涉及系统调用、栈内存分配、调度开销;
- 频繁创建新线程,容易造成:
- 线程数失控(成百上千个线程导致频繁上下文切换);
- 内存压力增大;
- 使用线程池可以:
- 复用线程,减少创建/销毁开销;
- 控制最大并发数,起到"限流阀门"的作用;
- 提供任务排队、拒绝策略等机制,提升系统稳定性。
Java 中线程池的核心类是:java.util.concurrent.ThreadPoolExecutor。
二、ThreadPoolExecutor 核心参数详解
关键构造方法:
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程闲置存活时间
TimeUnit unit, // 上述时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
下面逐个讲解。
1. corePoolSize:核心线程数
- 线程池会尽量维持这么多线程长期存在(除非设置了允许核心线程超时)。
- 当有新任务到达时:
- 当前线程数 <
corePoolSize→ 直接创建新线程执行(即便队列有空间); - 当前线程数 ≥
corePoolSize→ 任务先尝试入队。
- 当前线程数 <
经验值:
- CPU 密集型任务:
corePoolSize ≈ CPU 核数或N+1; - IO 密集型任务:
corePoolSize = CPU 核数 * 2甚至更多,根据 IO 阻塞比例调优。
2. maximumPoolSize:最大线程数
- 线程池允许创建的最大线程数。
- 触发扩容的条件:
- 当前线程数 ≥
corePoolSize; - 队列已满;
- 此时如果线程数 <
maximumPoolSize,则继续创建新线程执行任务。
- 当前线程数 ≥
注意:如果使用的是无界队列 (如默认的
LinkedBlockingQueue),队列基本不会满,maximumPoolSize几乎不会生效。
3. keepAliveTime + unit:线程空闲存活时间
- 当线程数 >
corePoolSize时,多出来的线程如果空闲时间超过keepAliveTime会被回收。 - 如果调用:
allowCoreThreadTimeOut(true),核心线程空闲也会被回收。
适合负载波动大的场景(高峰扩容,低谷回收)。
4. workQueue:任务队列(非常关键)
- 用于缓冲提交但是暂时没有可用线程执行的任务;
- 不同队列类型直接决定线程池的扩容、排队、拒绝行为;
- 常见选择:
ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue
具体对比见后文"队列选择"。
5. threadFactory:线程工厂
- 用于创建新线程;
- 一般用于:
- 自定义线程名称(便于定位问题);
- 设置为守护/非守护线程;
- 设置优先级等。
java
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName("biz-worker-" + t.getId());
return t;
};
线上强烈建议自定义线程名,日志/监控会友好很多。
6. RejectedExecutionHandler:拒绝策略
- 当队列已满 & 线程数已达
maximumPoolSize时,再提交新任务会触发; - 它定义了:线程池过载时,如何处理新任务;
- 详见"拒绝策略"一节。
三、队列选择:ArrayBlockingQueue vs LinkedBlockingQueue vs SynchronousQueue
workQueue 是 BlockingQueue<Runnable>,不同实现差异很大。
1. ArrayBlockingQueue:数组有界阻塞队列
java
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
特性:
- 必须指定容量,严格有界;
- 底层用数组,内存更紧凑,可预测性强;
- 入队/出队共享一把锁(经典实现),高并发下锁竞争略大;
- 可选公平性参数:
new ArrayBlockingQueue<>(capacity, fair)。
适用场景:
- 线程池任务队列推荐;
- 你希望:
- 限制排队任务数(避免 OOM);
- 排队上限清晰易控;
- 内存敏感、SLA 明确的系统,比如 Web 服务请求处理线程池。
2. LinkedBlockingQueue:链表阻塞队列(默认近似无界)
java
// 默认,无界(容量是 Integer.MAX_VALUE)
BlockingQueue<Runnable> q1 = new LinkedBlockingQueue<>();
// 有界形式
BlockingQueue<Runnable> q2 = new LinkedBlockingQueue<>(1000);
特性:
- 不指定容量时,近似无界;
- 底层链表,每个元素一个 Node 对象,内存占用相对更大;
- 内部使用两把锁(
putLock/takeLock),生产和消费大多可并行,吞吐量通常优于ArrayBlockingQueue。
适用场景:
- 一般生产者-消费者模型,对吞吐有要求;
- 用在线程池时一定要指定容量,否则任务会无限堆积,最终 OOM 或超长延迟。
3. SynchronousQueue:零容量队列
java
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
特性:
- 容量为 0,不存任务;
- 每次
put必须等待有线程take,是一种"任务直接交给线程"的模式; - 适合吞吐高、任务非常短的场景。
配合线程池行为:
- 队列不会存任务,因此:
- 当前线程数 <
maximumPoolSize时,会不断新建线程执行任务; - 达到
maximumPoolSize后,新的任务无法入队也无法扩容 → 触发拒绝策略。
- 当前线程数 <
JDK 内置的 Executors.newCachedThreadPool() 就是用的 SynchronousQueue。
四、拒绝策略:RejectedExecutionHandler
触发条件:线程数达到 maximumPoolSize 且队列已满。
JDK 提供了 4 种常用策略:
1. AbortPolicy(默认)------ 抛异常
java
new ThreadPoolExecutor.AbortPolicy()
行为:
- 直接抛出
RejectedExecutionException; - 如果上层没捕获,任务提交方会感知到异常。
适合:
- 希望显式暴露问题,调试/测试阶段尤其好用;
- 生产中也常配合上层快速失败 + 监控报警。
2. CallerRunsPolicy------ 由提交线程自己执行
java
new ThreadPoolExecutor.CallerRunsPolicy()
行为:
- 任务在提交任务的线程中执行;
- 不抛异常、不丢任务,但提交方会被"拖慢"。
效果:
- 一种自然限流 机制:
- 当线程池忙不过来时,提交线程被迫参与执行任务;
- 从源头减缓提交速率。
适合:
- 可以接受响应变慢,但不希望随意丢任务的场景。
3. DiscardPolicy------ 直接丢弃当前任务
java
new ThreadPoolExecutor.DiscardPolicy()
行为:
- 静默丢弃新提交的任务,不抛异常。
适用场景非常有限,只在"任务可有可无"并且"可以容忍丢"时才考虑使用。
4. DiscardOldestPolicy------ 丢队头旧任务,保新任务
java
new ThreadPoolExecutor.DiscardOldestPolicy()
行为:
- 丢弃队列中最旧的任务(队头),然后尝试入队当前任务;
- 同样不抛异常。
适合:
- "新任务比旧任务更有价值"的业务场景,比如频繁状态上报,只关心最新状态。
五、为什么不推荐直接使用 Executors 系列工厂方法?
常见工厂方法:
Executors.newFixedThreadPool(int nThreads)Executors.newSingleThreadExecutor()Executors.newCachedThreadPool()Executors.newScheduledThreadPool(int corePoolSize)
问题主要在于:隐藏了关键参数,默认策略不安全。
1. newFixedThreadPool / newSingleThreadExecutor
内部实现简化后大致如下:
java
// newFixedThreadPool
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()); // 无界队列
// newSingleThreadExecutor
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(
1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())); // 无界队列
问题:
- 使用的是无界队列
LinkedBlockingQueue(容量 =Integer.MAX_VALUE); - 当线程数固定后,多余任务全部排队:
- 生产速度 > 消费速度 → 队列无限增长;
- 极易导致 OOM 或者极长延迟(任务排队排到"天荒地老")。
2. newCachedThreadPool
内部大致实现:
java
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
问题:
- 无界最大线程数(
Integer.MAX_VALUE); - 队列为
SynchronousQueue,不存任务,提交一个来一个线程; - 短时间大量请求时,会疯狂创建新线程:
- 几千、几万个线程都不是问题;
- 非常容易把机器资源打满(上下文切换飙升、内存耗尽)。
3. 官方 & 规范建议
包括阿里巴巴 Java 开发手册在内的多家规范,都明确强调:
不要直接使用
Executors提供的快捷线程池工厂,需要使用
ThreadPoolExecutor手动创建线程池,显式指定参数。
六、推荐的线程池创建方式(模板)
一个比较通用的 Web 服务线程池模板:
java
import java.util.concurrent.*;
public class ThreadPoolConfig {
public static ExecutorService buildBizExecutor() {
int core = Runtime.getRuntime().availableProcessors();
int max = core * 2;
int queueCapacity = 1000;
long keepAlive = 60L;
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName("biz-exec-" + t.getId());
// t.setDaemon(false);
return t;
};
return new ThreadPoolExecutor(
core,
max,
keepAlive,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
factory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
特点:
- 核心线程数 ≈ CPU 核数;
- 有界
ArrayBlockingQueue限制队列长度,防止任务无限堆积; - 最大线程数为核心的 2 倍,应对突发流量;
- 使用
CallerRunsPolicy做自然限流。
七、ArrayBlockingQueue 与 LinkedBlockingQueue 的简单对比(面试可用)
问:在线程池里 ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别,使用场景?
回答要点:
- 结构与容量:
ArrayBlockingQueue:数组实现,必须指定容量,是严格有界队列,内存更紧凑;LinkedBlockingQueue:链表实现,不指定容量时近似无界,也可以指定容量变有界。
- 并发特性:
ArrayBlockingQueue:入队/出队用一把锁,高并发下锁竞争略大;LinkedBlockingQueue:入队/出队两把锁,生产/消费可高度并行,整体吞吐更好。
- 使用建议:
- 在线程池里,优先用有界队列 (
ArrayBlockingQueue或LinkedBlockingQueue(capacity)),防止任务堆积 OOM; - 一般的生产者-消费者队列、需要更高吞吐时,可以优先用
LinkedBlockingQueue,同样建议有限容量。
- 在线程池里,优先用有界队列 (
八、面试速记总结
- 线程池推荐使用
ThreadPoolExecutor手动创建,不要直接用Executors工厂方法。 - 核心参数:
corePoolSize:常驻线程数;maximumPoolSize:最大线程数,配合有界队列才有意义;workQueue:任务队列,优先有界;RejectedExecutionHandler:过载保护策略。
- 队列选择:
ArrayBlockingQueue:有界、数组、内存可控,适合线程池任务队列;LinkedBlockingQueue:链表、默认无界,注意避免在线程池中无限堆积任务。
- 拒绝策略:
- 默认
AbortPolicy:抛异常; - 常用
CallerRunsPolicy:提交线程自己执行,形成自然限流; - 其他两种丢弃策略慎用。
- 默认
- Executors 不推荐原因:
newFixedThreadPool&newSingleThreadExecutor:无界队列,易 OOM;newCachedThreadPool:最大线程数几乎无限,易创建过多线程压垮系统。