读完这篇,你会彻底搞懂线程池的每一个参数是干什么的、为什么需要阻塞队列、拒绝策略什么时候触发,以及在传统 Spring 和 Spring Boot 中分别怎么配。
一、线程池能干什么
一句话:复用线程,减少创建销毁的开销。
打个比方。你开了一家银行网点:
没有线程池(每次 new Thread):
来一个客户 → 招一个柜员 → 办完 → 开除
再来一个 → 再招一个柜员 → 办完 → 开除
↑ 招人和开除的成本比办业务本身还高
有线程池:
先招 5 个柜员常驻窗口(核心线程)
来客户了 → 喊个柜员去办 → 办完回窗口等着(复用)
客户太多 → 从备班名单里再拉几个柜员(扩容到最大线程数)
大厅站不下了 → 按规矩来(拒绝策略)
线程池解决三个问题:
| 问题 | 没线程池 | 有线程池 |
|---|---|---|
| 资源消耗 | 每个任务 new 一个线程,用完销毁 | 线程复用,不重复创建 |
| 响应速度 | new Thread 需要时间 | 线程就绪,来了直接用 |
| 管理能力 | 线程数不可控,内存可能爆 | 统一管理,有上限 |
二、核心概念
先看一张全景图:

2.1 任务(Task)------"客户的业务"
// Runnable:干完活没返回值
Runnable task = () -> 办存款();
// Callable:干完活有返回值
Callable<String> task = () -> { 办存款(); return "已入账"; };
2.2 核心线程数(corePoolSize)------"常驻窗口柜员"
不管有没有客户,这 N 个柜员一直在窗口守着。 (除非设置了 allowCoreThreadTimeOut)
// 核心线程 = 5,相当于大厅常设 5 个窗口,柜员准时上班
// 没客户的时候他们就在窗口等着,不会走
2.3 阻塞队列(BlockingQueue)------"大厅等候区"
窗口全满,新来的客户先取号排队。
5 个窗口全在办业务
↓
第 6 个客户来了 → 取号,在等候区坐着等 ← 阻塞队列
第 7 个客户来了 → 取号,在等候区坐着等
...
等候区坐满了! ← 触发扩容
三种常用队列:
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
LinkedBlockingQueue |
有界/无界,FIFO 排队 | 通用 |
ArrayBlockingQueue |
有界,FIFO | 必须限制排队人数时 |
SynchronousQueue |
没有等候区,来了必须立刻办 | 要求立即处理的场景 |
关键:等候区坐满了才会开新窗口。 所以你的等候区设 5000 个座位,意味着前 5005 个客户都是 5 个窗口在办(5个在办 + 5000个在等)。
2.4 最大线程数(maxPoolSize)------"所有窗口全开 + 备班全上"
5 个窗口全忙 + 等候区 5000 个座位全满
↓
第 5006 个客户:没座位了!→ 从备班名单拉人,开新窗口
↓
一直开到 maxPoolSize = 50 个窗口
↓
50 个窗口全在办 + 5000 个在等 = 5050 个客户同时处理中
↓
第 5051 个客户:没窗口、没座位 → 拒绝策略
2.5 拒绝策略(RejectedExecutionHandler)------"大厅站都站不下了怎么办"
四种策略:
AbortPolicy(默认)
"暂停营业!" → 直接抛 RejectedExecutionException
适用:必须保证任务不能丢的场景
CallerRunsPolicy
"大堂经理亲自办!" → 提交任务的线程自己执行
适用:可以接受延迟,但不能丢任务
DiscardPolicy
"不接了!" → 直接拒绝,不打任何招呼
适用:可有可无的非关键任务
DiscardOldestPolicy
"最早取号的那个不办了!" → 请最早排队的走,给新来的腾位置
适用:宁愿丢旧数据,也要处理最新的
2.6 存活时间(keepAliveTime)------"备班柜员多久没活就下班"
常驻柜员:一直上班,不走(除非 allowCoreThreadTimeOut = true)
备班柜员:空闲超过 keepAliveTime 秒,下班走人
窗口数变化曲线:
50 ┤ ╭─╮
│ ╱ ╲
20 ┤── ╱ ╲──── keepAliveTime 到期,备班柜员下班 ────
5 ┤───────────────────────────────────────────(常驻窗口不变)
└────────────────────────────────────────────→ 时间
忙 闲 忙 闲
三、执行流程------任务提交后到底走了哪条路
客户来了 ------ submit(task)
│
▼
┌─ 有窗口空着? ── YES ──→ 柜员直接办
│ │ NO
│ ▼
│ 等候区有座位? ── YES ──→ 取号排队等
│ │ NO
│ ▼
│ 当前窗口数 < maxPoolSize? ── YES ──→ 开新窗口,备班柜员上
│ │ NO
│ ▼
└──→ 执行拒绝策略
关键认知:扩容的发生条件是"核心线程全忙 + 队列全满",而不是"核心线程全忙"。这意味着:
核心线程=5,最大线程=50,等候区=5000 个座位
客户数 1-5: 直接开窗口办
客户数 6-5005: 取号坐等候区等(只开了 5 个窗口!)
客户数 5006+: 才开始开 6-50 号窗口
所以如果你设了很大的队列(比如 Integer.MAX_VALUE),最大线程数永远不会用到。
四、原生 Java 实现(不用 Spring)
java
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ManualThreadPoolDemo {
public static void main(String[] args) throws Exception {
// ========== 1. 自定义线程工厂 ==========
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("bank-counter-" + count.getAndIncrement()); // 线程名
t.setDaemon(false); // 非守护线程
t.setUncaughtExceptionHandler((thread, ex) -> {
System.err.println(thread.getName() + " 异常: " + ex.getMessage());
});
return t;
}
};
// ========== 2. 创建线程池 ==========
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
30L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 阻塞队列,容量 100
threadFactory, // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// ========== 3. 提交任务 ==========
// 方式一:Runnable,没返回值
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办了一笔存款");
});
// 方式二:Callable + Future,有返回值
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "存款已入账";
});
System.out.println("等待办理结果...");
String result = future.get(); // 阻塞等待
System.out.println(result);
// ========== 4. 关闭线程池 ==========
executor.shutdown(); // 不再接新任务,已提交的跑完
// executor.shutdownNow(); // 立刻中断所有任务
boolean terminated = executor.awaitTermination(10, TimeUnit.SECONDS);
if (terminated) {
System.out.println("线程池已正常关闭");
}
}
}
五、Spring Boot 实现
注意,springboot里子自带了一个配置好的线程池,大部分场景可以直接使用,不用写config配置类,但拒绝策略是固定的。所以下面演示自定义线程池写法。
5.1 配置文件(application.yml)
java
thread:
pool:
executor:
config:
core-pool-size: 10 # 核心线程数
max-pool-size: 50 # 最大线程数
keep-alive-time: 30 # 空闲存活时间(秒)
block-queue-size: 200 # 队列容量
policy: CallerRunsPolicy # 拒绝策略
5.2 配置类(读取 YML 配置)
java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "thread.pool.executor.config")
public class ThreadPoolConfigProperties {
private Integer corePoolSize = 10;
private Integer maxPoolSize = 50;
private Long keepAliveTime = 30L;
private Integer blockQueueSize = 200;
private String policy = "CallerRunsPolicy";
}
5.3 线程池 Bean 注册
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Configuration
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties props) {
// 拒绝策略
RejectedExecutionHandler handler;
switch (props.getPolicy()) {
case "DiscardPolicy":
handler = new ThreadPoolExecutor.DiscardPolicy();
break;
case "DiscardOldestPolicy":
handler = new ThreadPoolExecutor.DiscardOldestPolicy();
break;
case "CallerRunsPolicy":
handler = new ThreadPoolExecutor.CallerRunsPolicy();
break;
default:
handler = new ThreadPoolExecutor.AbortPolicy();
}
// 线程工厂
ThreadFactory factory = new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("biz-pool-" + count.getAndIncrement());
t.setUncaughtExceptionHandler((thread, ex) ->
log.error("线程池异常 {}", thread.getName(), ex)
);
return t;
}
};
return new ThreadPoolExecutor(
props.getCorePoolSize(),
props.getMaxPoolSize(),
props.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(props.getBlockQueueSize()),
factory,
handler
);
}
}
5.4 使用线程池
java
@Service
public class MyService {
@Resource
private ThreadPoolExecutor threadPoolExecutor;
public void doAsyncWork() throws Exception {
// 无返回值
threadPoolExecutor.execute(() -> {
System.out.println("异步执行");
});
// 有返回值 + CompletableFuture 链式处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 业务逻辑
return "结果";
}, threadPoolExecutor);
future.thenAccept(result -> {
System.out.println("拿到结果:" + result);
});
}
}