这篇文章在讲什么
这篇文章会带你从零开始理解 Java 线程池。
读完后你会明白:
为什么需要线程池?直接 new Thread 不行吗?
线程池的 7 个核心参数分别是什么?各自的作用?
线程池内部的工作流程是怎样的?
常见的线程池类型有哪些?各自适用什么场景?
Spring 中怎么使用线程池?
生产环境中线程池应该怎么配置?
面试中高频问题怎么回答?
在开始之前,先问你几个问题
问题1:如果一个系统每秒有 1000 个请求,每个请求需要并发处理,
你能每个请求都 new Thread() 吗?会有什么问题?
问题2:线程池的 7 个参数你能说出来吗?它们之间是什么关系?
问题3:corePoolSize 和 maximumPoolSize 都是 10,队列满了之后会怎样?
如果 corePoolSize 是 5,maximumPoolSize 是 10,队列满了之后又会怎样?
问题4:Spring 中 @Async 注解默认用的是什么线程池?为什么生产环境不推荐用默认的?
问题5:线程池的拒绝策略有几种?各自的行为是什么?
带着这些问题往下读。
第一部分:为什么需要线程池
一、从一个最简单的并发场景开始
假设你有一个 Web 接口,每个请求需要查询三个远程服务:
// 串行执行:一个一个查,总耗时 = 300 + 200 + 500 = 1000ms
public OrderDetail getOrderDetail(Long orderId) {
Order order = orderService.query(orderId); // 300ms
User user = userService.query(order.getUserId()); // 200ms
List<Item> items = itemService.query(orderId); // 500ms
return new OrderDetail(order, user, items);
}
如果改成并发执行:
// 并发执行:三个同时查,总耗时 = max(300, 200, 500) = 500ms
public OrderDetail getOrderDetail(Long orderId) {
Future<Order> orderFuture = executor.submit(() -> orderService.query(orderId));
Future<User> userFuture = executor.submit(() -> userService.query(order.getUserId()));
Future<List<Item>> itemsFuture = executor.submit(() -> itemService.query(orderId));
Order order = orderFuture.get();
User user = userFuture.get();
List<Item> items = itemsFuture.get();
return new OrderDetail(order, user, items);
}
耗时从 1000ms 降到 500ms。 这里的 executor 就是线程池。
二、为什么不直接 new Thread?
// 最原始的方式:每个任务创建一个线程
new Thread(() -> orderService.query(orderId)).start();
new Thread(() -> userService.query(userId)).start();
new Thread(() -> itemService.query(orderId)).start();
看起来能用,但生产环境完全不行。问题有三个:
问题一:创建和销毁线程的开销很大
创建一个线程需要做什么?
1. 在 JVM 中分配内存(每个线程默认 1MB 栈空间)
2. 在操作系统内核中创建线程(系统调用)
3. 上下文切换的准备工作
销毁一个线程需要做什么?
1. 释放 JVM 内存
2. 通知操作系统回收资源
这些操作的耗时大约在 0.1ms ~ 1ms 之间。
听起来不多?算一笔账:
假设你的系统每秒处理 1000 个请求
每个请求创建 3 个线程
每秒创建 3000 个线程
创建开销:3000 × 0.5ms = 1500ms
每个线程 1MB 栈空间:3000 × 1MB = 3GB 内存
而且这些线程执行完就销毁了,下次请求又重新创建。
大量时间花在了创建和销毁线程上,而不是执行业务逻辑。
问题二:无法控制并发数量
// 假设突然来了 10000 个请求
for (int i = 0; i < 10000; i++) {
new Thread(() -> doSomething()).start();
// 瞬间创建 10000 个线程
}
10000 个线程 × 1MB 栈空间 = 10GB 内存
操作系统能创建的线程数有上限(通常几千到几万)
超过限制 → OutOfMemoryError: unable to create new native thread
服务器直接崩溃
问题三:没有统一管理
线程创建了就不管了
不知道有多少线程在运行
不知道哪些任务在排队
无法优雅地关闭
无法监控线程状态
三、线程池的思路:复用线程
线程池的核心思路:
不是每个任务创建一个线程,而是预先创建一批线程,放在一个"池子"里。
有任务来了,从池子里取一个线程来执行。
执行完后,线程不销毁,放回池子里,等下一个任务。
就像餐厅的服务员:
不是每个客人来了临时招聘一个服务员,用完就辞退
而是预先雇佣一批服务员,哪个客人需要就派哪个过去
没有线程池:
请求1 → 创建线程A → 执行 → 销毁线程A
请求2 → 创建线程B → 执行 → 销毁线程B
请求3 → 创建线程C → 执行 → 销毁线程C
有了线程池:
启动时 → 创建线程A、B、C 放在池子里
请求1 → 取线程A → 执行 → 线程A 放回池子
请求2 → 取线程A → 执行 → 线程A 放回池子
请求3 → 取线程B → 执行 → 线程B 放回池子
线程被复用了,创建和销毁的开销只发生一次。
第二部分:线程池的 7 个核心参数
一、先看创建线程池的代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // unit
new ArrayBlockingQueue<>(100), // workQueue
new ThreadFactory() { ... }, // threadFactory
new ThreadPoolExecutor.AbortPolicy() // handler
);
这 7 个参数就是线程池的全部配置。 逐个拆解:
二、逐个解释
参数 1:corePoolSize(核心线程数)
线程池中始终保持存活的线程数量,即使它们处于空闲状态。
比如 corePoolSize = 5:
线程池启动后,即使没有任何任务,也会保持 5 个线程存活。
有任务来了,优先用这 5 个线程来执行。
任务执行完,这 5 个线程不销毁,继续等待下一个任务。
参数 2:maximumPoolSize(最大线程数)
线程池允许创建的最大线程数量。
比如 maximumPoolSize = 10:
最多同时运行 10 个线程。
核心线程 5 个都在忙,队列也满了,才会创建额外的线程(最多再创建 5 个)。
额外的线程空闲超过 keepAliveTime 后会被销毁。
参数 3:keepAliveTime(空闲线程存活时间)
非核心线程空闲时的存活时间。
比如 keepAliveTime = 60 秒:
额外创建的线程(第 6~10 个)如果 60 秒内没有新任务,就会被销毁。
核心线程不受这个参数影响(默认情况下核心线程不会被销毁)。
参数 4:unit(时间单位)
keepAliveTime 的时间单位。
TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
参数 5:workQueue(任务队列)
核心线程都在忙时,新任务放在这个队列里等待。
比如 workQueue = new ArrayBlockingQueue<>(100):
最多容纳 100 个等待的任务。
核心线程执行完当前任务后,从队列里取下一个任务来执行。
参数 6:threadFactory(线程工厂)
用来创建新线程的工厂。
可以自定义线程名称、优先级等。
为什么需要自定义?
默认创建的线程名字是 "pool-1-thread-1"、"pool-1-thread-2"
出了问题看日志,根本不知道是哪个线程池的线程
自定义命名后:"order-pool-thread-1"、"payment-pool-thread-2"
一目了然。
// 自定义线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
private int count = 0;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("order-pool-thread-" + (++count));
return thread;
}
};
参数 7:handler(拒绝策略)
线程池满了(线程数达到 maximumPoolSize,队列也满了)时,
新提交的任务怎么处理?
4 种内置拒绝策略:
AbortPolicy(默认) → 直接抛出 RejectedExecutionException 异常
CallerRunsPolicy → 由提交任务的线程自己来执行这个任务
DiscardPolicy → 直接丢弃任务,不抛异常
DiscardOldestPolicy → 丢弃队列中最早的任务,把新任务放进队列
三、7 个参数的关系——用一张图理解
你提交一个任务
↓
当前运行的线程数 < corePoolSize?
├── 是 → 创建一个核心线程来执行任务
└── 否 → 把任务放进 workQueue
↓
workQueue 没满?
├── 是 → 任务在队列里排队等待
└── 否(队列满了)
↓
当前线程数 < maximumPoolSize?
├── 是 → 创建一个非核心线程来执行任务
└── 否(线程数也满了)→ 执行拒绝策略
用一段伪代码表示:
public void execute(Runnable task) {
if (当前线程数 < corePoolSize) {
创建新线程执行任务;
} else if (workQueue.offer(task)) {
// 队列没满,任务入队等待
} else if (当前线程数 < maximumPoolSize) {
创建非核心线程执行任务;
} else {
执行拒绝策略;
}
}
四、一个完整的例子
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程 5 个
10, // 最多 10 个线程
60, TimeUnit.SECONDS, // 非核心线程空闲 60 秒后销毁
new ArrayBlockingQueue<>(100), // 队列容量 100
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
场景模拟:
提交第 1~5 个任务:
→ 核心线程数 5,当前 0 个,创建 5 个核心线程执行
→ 状态:5 个线程在运行,队列空
提交第 6~105 个任务(假设前 5 个还没执行完):
→ 核心线程都在忙,任务进入队列
→ 状态:5 个线程在运行,队列里 100 个任务在等
提交第 106 个任务:
→ 核心线程都在忙,队列也满了(100 个)
→ 当前线程数 5 < 最大线程数 10
→ 创建第 6 个线程(非核心线程)来执行
→ 状态:6 个线程在运行,队列满
提交第 107~110 个任务:
→ 同上,继续创建非核心线程
→ 状态:10 个线程在运行,队列满
提交第 111 个任务:
→ 核心线程都在忙,队列满了,线程数也到最大了
→ 执行拒绝策略:CallerRunsPolicy
→ 由提交任务的线程(比如主线程)自己来执行这个任务
第三部分:常见的线程池类型
一、Executors 工具类提供的线程池
Java 提供了一个 Executors 工具类,可以快速创建线程池:
FixedThreadPool(固定大小线程池)
ExecutorService executor = Executors.newFixedThreadPool(5);
corePoolSize = 5
maximumPoolSize = 5(和核心线程数一样)
keepAliveTime = 0(不会超时,因为都是核心线程)
workQueue = LinkedBlockingQueue(无界队列,容量无限)
特点:线程数固定,不会增加也不会减少。
问题:队列是无界的(LinkedBlockingQueue 没有容量限制)。如果任务提交速度超过处理速度,队列会无限增长,最终导致内存溢出。
CachedThreadPool(缓存线程池)
ExecutorService executor = Executors.newCachedThreadPool();
corePoolSize = 0(没有核心线程)
maximumPoolSize = Integer.MAX_VALUE(无限大)
keepAliveTime = 60 秒
workQueue = SynchronousQueue(不存储任务,直接交给线程执行)
特点:有任务就创建线程,线程空闲 60 秒后销毁。
问题:maximumPoolSize 是无限大。如果瞬间提交大量任务,会创建大量线程,可能导致系统资源耗尽。
SingleThreadExecutor(单线程线程池)
ExecutorService executor = Executors.newSingleThreadExecutor();
corePoolSize = 1
maximumPoolSize = 1
workQueue = LinkedBlockingQueue(无界队列)
特点:只有一个线程,所有任务串行执行。保证任务按提交顺序执行。
ScheduledThreadPool(定时线程池)
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
特点:支持定时任务和周期性任务。
// 延迟 3 秒后执行
executor.schedule(() -> System.out.println("延迟任务"), 3, TimeUnit.SECONDS);
// 每隔 5 秒执行一次
executor.scheduleAtFixedRate(() -> System.out.println("周期任务"),
0, 5, TimeUnit.SECONDS);
二、为什么阿里规范禁止使用 Executors 创建线程池
阿里巴巴 Java 开发手册明确规定:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式创建。
原因:
FixedThreadPool:
workQueue = LinkedBlockingQueue(无界队列)
→ 队列无限增长 → 内存溢出
CachedThreadPool:
maximumPoolSize = Integer.MAX_VALUE
→ 线程无限创建 → 系统资源耗尽
SingleThreadExecutor:
workQueue = LinkedBlockingQueue(无界队列)
→ 同样有内存溢出风险
正确做法:用 ThreadPoolExecutor 手动创建,明确指定所有参数。
// ✅ 正确做法
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列,不会无限增长
new ThreadPoolExecutor.CallerRunsPolicy()
);
三、对比总结
线程池类型 核心线程 最大线程 队列类型 问题
──────────────────────────────────────────────────────────
FixedThreadPool N N LinkedBlockingQueue 无界队列,可能OOM
CachedThreadPool 0 MAX SynchronousQueue 无限线程,可能耗尽资源
SingleThreadExecutor 1 1 LinkedBlockingQueue 无界队列,可能OOM
ThreadPoolExecutor 自定义 自定义 自定义 无,自己控制
第四部分:线程池的工作流程详解
一、execute() 方法的完整流程
executor.execute(new Runnable() {
@Override
public void run() {
// 你的任务代码
}
});
当你调用 execute() 提交一个任务时,线程池内部的处理流程:
public void execute(Runnable command) {
// 第1步:获取当前工作线程数
int c = ctl.get();
int workerCount = workerCountOf(c);
// 第2步:如果当前线程数 < 核心线程数
if (workerCount < corePoolSize) {
// 创建一个核心线程来执行这个任务
if (addWorker(command, true)) // true 表示核心线程
return;
// 创建失败(比如线程池已关闭),重新获取状态
c = ctl.get();
}
// 第3步:核心线程都在忙,尝试把任务放进队列
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);
}
// 第4步:队列满了,尝试创建非核心线程
else if (!addWorker(command, false)) // false 表示非核心线程
// 创建失败(线程数已到最大值),执行拒绝策略
reject(command);
}
二、addWorker() 做了什么
private boolean addWorker(Runnable firstTask, boolean core) {
// 创建一个 Worker 对象(Worker 包装了线程和任务)
Worker worker = new Worker(firstTask);
Thread thread = worker.thread;
// 启动线程
thread.start();
// Worker 的 run() 方法会循环从队列里取任务执行
}
三、Worker 的工作循环
// Worker.run()(简化版)
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Runnable task = w.firstTask; // 第一个任务
while (task != null || (task = getTask()) != null) {
// getTask() 从队列里取任务
// 如果队列为空,getTask() 会阻塞等待
try {
task.run(); // 执行任务
} finally {
task = null;
}
}
// 循环退出:线程池关闭了,或者非核心线程超时了
// 这个线程结束,被回收
}
关键点:getTask() 会从 workQueue 里取任务。如果队列为空,核心线程会一直阻塞等待,非核心线程等待超过 keepAliveTime 后返回 null,线程退出循环,被销毁。
四、submit() 和 execute() 的区别
// execute():提交 Runnable,没有返回值
executor.execute(() -> doSomething());
// submit():提交 Callable 或 Runnable,返回 Future
Future<String> future = executor.submit(() -> {
return doSomethingAndReturn();
});
String result = future.get(); // 阻塞等待结果
execute() → 提交任务,不关心结果
submit() → 提交任务,通过 Future 获取结果或异常
submit() 底层也是调用 execute(),只是包装了一层 FutureTask。
第五部分:线程池的监控和调优
一、线程池提供的监控方法
ThreadPoolExecutor executor = ...;
executor.getPoolSize() // 当前线程池中的线程数
executor.getActiveCount() // 正在执行任务的线程数
executor.getCompletedTaskCount() // 已完成的任务数
executor.getTaskCount() // 已提交的任务总数(包括已完成和正在执行的)
executor.getQueue().size() // 队列中等待的任务数
executor.getQueue().remainingCapacity() // 队列剩余容量
executor.getLargestPoolSize() // 历史最大线程数
二、一个实际的监控示例
@Component
public class ThreadPoolMonitor {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolMonitor.class);
@Autowired
private ThreadPoolExecutor executor;
@Scheduled(fixedRate = 5000) // 每 5 秒打印一次
public void monitor() {
log.info("线程池状态:线程数={}, 活跃线程={}, 队列等待={}, 已完成={}, 历史最大={}",
executor.getPoolSize(),
executor.getActiveCount(),
executor.getQueue().size(),
executor.getCompletedTaskCount(),
executor.getLargestPoolSize()
);
}
}
输出:
线程池状态:线程数=8, 活跃线程=5, 队列等待=23, 已完成=1547, 历史最大=10
三、线程池参数怎么配置
没有万能公式,但有一些经验法则:
CPU 密集型任务
任务特点:计算量大,一直在使用 CPU(比如加密、压缩、图像处理)
线程数 = CPU 核心数 + 1
为什么?CPU 密集型任务每个线程都在用 CPU,
线程数超过 CPU 核心数只会增加上下文切换的开销,不会提高效率。
+1 是为了当某个线程因为偶尔的页缺失或其他原因暂停时,多出来的线程可以利用 CPU。
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCores + 1, cpuCores + 1, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200)
);
IO 密集型任务
任务特点:大量时间在等待 IO(比如网络请求、数据库查询、文件读写)
线程数 = CPU 核心数 × 2 或 CPU 核心数 / (1 - 阻塞系数)
为什么?IO 密集型任务在等待 IO 时 CPU 是空闲的,
可以多开线程让 CPU 在等待期间处理其他任务。
阻塞系数 = 等待时间 / 总时间
如果一个任务 80% 时间在等 IO,20% 在用 CPU:
线程数 = CPU 核心数 / (1 - 0.8) = CPU 核心数 × 5
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCores * 2, cpuCores * 2, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500)
);
实际生产环境的经验值
场景 建议核心线程数 建议最大线程数 队列大小
───────────────────────────────────────────────────────────────
HTTP 请求处理 CPU核心数 × 2 CPU核心数 × 4 200~500
数据库批量操作 5~10 10~20 100~200
消息消费 消费者数量 消费者数量 × 2 50~100
异步通知 3~5 5~10 100~200
核心原则:先用小参数上线,通过监控数据逐步调优。不要一上来就设很大的值。
第六部分:Spring 中使用线程池
一、Spring 中配置线程池
@Configuration
public class ThreadPoolConfig {
@Bean("orderExecutor")
public ThreadPoolExecutor orderExecutor() {
return new ThreadPoolExecutor(
5,
10,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("order-pool-" + count.incrementAndGet());
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
二、使用 @Async 异步执行
@Service
public class OrderService {
@Async("orderExecutor") // 指定使用哪个线程池
public void sendNotification(Order order) {
// 这个方法会在 orderExecutor 线程池中异步执行
notificationClient.send(order);
}
}
// 调用方
public void placeOrder(Order order) {
orderRepository.save(order);
orderService.sendNotification(order); // 异步执行,不阻塞
return "下单成功"; // 立即返回,不用等通知发送完
}
注意:@Async 基于 AOP 代理,和 @Transactional 一样有同类调用不生效的问题。
三、@Async 默认线程池的问题
// 如果不指定线程池,@Async 使用 Spring 默认的 SimpleAsyncTaskExecutor
@Async // 没有指定线程池
public void sendNotification(Order order) { ... }
SimpleAsyncTaskExecutor 的问题:
每次调用都创建一个新线程,不复用
等于没有线程池
高并发下会创建大量线程,系统崩溃
生产环境必须自定义线程池并指定。
四、CompletableFuture + 线程池(现代并发编程)
@Service
public class OrderService {
@Autowired
private ThreadPoolExecutor orderExecutor;
public OrderDetail getOrderDetail(Long orderId) {
// 三个查询并发执行
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(
() -> orderRepository.findById(orderId), orderExecutor);
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
() -> userService.findByOrderId(orderId), orderExecutor);
CompletableFuture<List<Item>> itemsFuture = CompletableFuture.supplyAsync(
() -> itemService.findByOrderId(orderId), orderExecutor);
// 等待所有结果
CompletableFuture.allOf(orderFuture, userFuture, itemsFuture).join();
// 组装结果
return new OrderDetail(
orderFuture.join(),
userFuture.join(),
itemsFuture.join()
);
}
}
// 更强大的链式操作
CompletableFuture.supplyAsync(() -> orderRepository.findById(orderId), orderExecutor)
.thenApply(order -> {
// 拿到订单后,查用户
User user = userService.findByUserId(order.getUserId());
return new OrderDetail(order, user);
})
.thenApply(detail -> {
// 拿到订单+用户后,查商品
List<Item> items = itemService.findByOrderId(detail.getOrder().getId());
detail.setItems(items);
return detail;
})
.exceptionally(ex -> {
// 任何一步出错,走这里
log.error("查询订单详情失败", ex);
return null;
});
第七部分:常见问题和坑
一、线程池中的异常处理
executor.execute(() -> {
int result = 1 / 0; // 抛出 ArithmeticException
});
execute() 提交的任务:
异常会直接抛出,打印到控制台
如果不捕获,这个线程会终止
线程池会创建一个新线程来替代它
submit() 提交的任务:
异常不会直接抛出,被包装在 Future 里
调用 future.get() 时才会抛出 ExecutionException
如果不调用 future.get(),异常会被静默吞掉
// submit() 的正确用法
Future<String> future = executor.submit(() -> {
return doSomething();
});
try {
String result = future.get();
} catch (ExecutionException e) {
// 通过 getCause() 拿到真正的异常
Throwable cause = e.getCause();
log.error("任务执行失败", cause);
}
二、线程池的优雅关闭
// ❌ 错误:直接关闭
executor.shutdownNow(); // 立即中断所有线程,正在执行的任务也会被中断
// ✅ 正确:优雅关闭
executor.shutdown(); // 不再接受新任务,等待已提交的任务执行完
boolean terminated = executor.awaitTermination(30, TimeUnit.SECONDS);
if (!terminated) {
// 30 秒内还有任务没执行完,强制关闭
executor.shutdownNow();
}
shutdown() vs shutdownNow()
shutdown():
不再接受新任务
已提交的任务会继续执行完
正在执行的任务不会被中断
shutdownNow():
不再接受新任务
尝试中断正在执行的任务
返回队列中还没执行的任务列表
三、ThreadLocal 和线程池的坑
// ThreadLocal 存储用户信息
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
// 在请求处理中设置
currentUser.set(user);
// 在业务代码中获取
User user = currentUser.get();
问题:线程池中的线程是复用的。
请求1 → 线程A 执行 → currentUser.set(user1)
请求1 结束 → 线程A 放回池子 → currentUser 里还有 user1
请求2 → 线程A 执行 → currentUser.get() 拿到的是 user1 ❌
应该是 user2,但上一个请求没清理
解决:用完必须清理。
try {
currentUser.set(user);
// 业务逻辑
} finally {
currentUser.remove(); // 必须清理
}
更好的方案:用拦截器统一处理。
@Component
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
User user = extractUser(request);
UserContext.set(user); // 设置
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
UserContext.remove(); // 清理
}
}
第八部分:回顾与检验
回顾
你从这篇文章学到了什么:
1. 为什么需要线程池
- 创建和销毁线程有开销,线程池复用线程
- 控制并发数量,防止系统资源耗尽
- 统一管理和监控
2. 7 个核心参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程空闲存活时间
- unit:时间单位
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
3. 工作流程
- 核心线程未满 → 创建核心线程
- 核心线程满了 → 任务入队
- 队列满了 → 创建非核心线程
- 线程数到最大 → 执行拒绝策略
4. 4 种拒绝策略
- AbortPolicy:抛异常
- CallerRunsPolicy:调用者自己执行
- DiscardPolicy:静默丢弃
- DiscardOldestPolicy:丢弃最老的任务
5. Spring 中使用
- 自定义 ThreadPoolExecutor Bean
- @Async 指定线程池
- CompletableFuture 链式并发
现在回答开头的问题
问题1:每秒 1000 个请求,每个都 new Thread() 会怎样?
会创建大量线程,每个线程占 1MB 栈空间,很快耗尽内存。而且创建和销毁线程的开销远大于执行任务本身的开销。系统会崩溃。
问题2:7 个参数是什么?
corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
问题3:corePoolSize 和 maximumPoolSize 都是 10,队列满了会怎样?
直接执行拒绝策略,因为没有空间再创建非核心线程了(已经到最大值了)。如果 core=5、max=10、队列满了,会创建非核心线程(第 6~10 个)来处理。
问题4:@Async 默认用什么线程池?
SimpleAsyncTaskExecutor,每次调用都创建新线程,不复用。生产环境必须自定义线程池。
问题5:拒绝策略有几种?
四种:AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最老的)。