文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
王二的订单系统又崩了。这次不是线程太多,是用了 Executors.newCachedThreadPool (),高峰期日志里全是 “OutOfMemoryError: unable to create new native thread”—— 线程数飙到了几万,JVM 内存直接炸了。领导把他骂得狗血淋头:“上次让你学 ThreadPoolExecutor,你怎么还在用 Executors?再出问题,你就卷铺盖走人!”
王二抱着头蹲在工位旁,哇哥走过来,踢了踢他的凳子:“早告诉你 Executors 是陷阱,你偏不听。今天教你 Executor 的实战优化,从批量任务处理到线程池监控,全是生产环境能用的干货,再翻车我替你背锅。”
点赞 + 关注,跟着哇哥和王二,吃透 Executor 实战技巧,生产环境再也不踩坑!

一、王二的致命坑:Executors 的 “甜蜜陷阱”

王二的订单系统,用 Executors.newCachedThreadPool () 处理批量订单,代码长这样:
package cn.tcmeta.threadpoolexecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 王二的坑:用Executors.newCachedThreadPool导致OOM
* @version: 1.0.0
*/
public class ExecutorsDisasterSample {
// 处理订单任务
private static void processOrder(String orderId) {
try {
TimeUnit.MILLISECONDS.sleep(100); // 模拟处理耗时
System.out.println(Thread.currentThread().getName() + ":处理订单" + orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static void main() {
// 陷阱:newCachedThreadPool的最大线程数是Integer.MAX_VALUE
ExecutorService executor = Executors.newCachedThreadPool();
// 模拟10000个订单任务(高峰期会更多)
for (int i = 0; i < 10000; i++) {
String orderId = "ORDER_" + i;
executor.submit(() -> processOrder(orderId));
}
executor.shutdown();
}
}
运行起来,线程数从几十涨到几万,很快就 OOM 了。“newCachedThreadPool 不是‘缓存线程池’吗?怎么会创建这么多线程?” 王二不解。
“缓存线程池是‘缓存空闲线程’,不是‘限制线程数’,” 哇哥恨铁不成钢,“它的 corePoolSize 是 0,maximumPoolSize 是 Integer.MAX_VALUE,任务一来就创建新线程,空闲 60 秒才销毁。高峰期 10000 个任务同时来,就创建 10000 个线程,内存不炸才怪 —— 这就是 Executors 的陷阱,看似方便,实则埋了雷。”
二、用 “医院挂号” 讲透 Executor 实战优化:可控才是王道
哇哥拿医院挂号的例子,给王二讲实战优化的核心:“医院不会无限制加挂号窗口(线程),而是会设置号源池(队列)、挂号员数量(核心线程),人太多就告诉患者‘号满了’(拒绝策略)——Executor 优化也是一个道理:控制线程数、限制队列容量、做好监控。”

💯 Executor 实战优化 4 大核心(王二记在病历本上)
- 拒绝 Executors,用自定义 ThreadPoolExecutor:手动控制核心参数,避免 OOM;
- 线程池隔离:不同业务用不同线程池(比如订单线程池、日志线程池),避免一个业务崩了影响全局;
- 批量任务处理:用 invokeAll、invokeAny 处理批量任务,提高效率;
- 线程池监控:监控线程池状态(活跃线程数、队列任务数),提前发现问题。
三、实战 1:线程池隔离 + 批量任务处理(可直接抄)

哇哥帮王二改造了订单系统,用线程池隔离和 invokeAll 处理批量订单,代码如下:
package cn.tcmeta.threadpoolexecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @author: laoren
* @description: 实战:线程池隔离+批量任务处理
* @version: 1.0.0
*/
public class ExecutorBatchSample {
// 1. 订单业务专用线程池(隔离)
private static final ThreadPoolExecutor ORDER_THREAD_POOL = new ThreadPoolExecutor(
10, // 核心线程数(CPU核心数*2)
20, // 最大线程数
60, // 空闲时间60秒
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200), // 有界队列,容量200
r -> new Thread(r, "order-pool-"),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用线程自己处理
);
// 2. 日志业务专用线程池(隔离,和订单池互不干扰)
private static final ThreadPoolExecutor LOG_THREAD_POOL = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
r -> new Thread(r, "log-pool-"),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
// 处理单个订单(有返回值,用Callable)
private static Callable<String> processOrderTask(String orderId) {
return () -> {
TimeUnit.MILLISECONDS.sleep(100);
String result = Thread.currentThread().getName() + ":订单" + orderId + "处理完成";
// 处理完订单,记录日志(用日志线程池)
LOG_THREAD_POOL.submit(() -> logOrderResult(orderId, result));
return result;
};
}
// 记录订单处理日志
private static void logOrderResult(String orderId, String result) {
try {
TimeUnit.MILLISECONDS.sleep(50);
System.out.println(Thread.currentThread().getName() + ":日志 - " + result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 批量处理订单
public static List<String> batchProcessOrders(List<String> orderIds) throws ExecutionException, InterruptedException {
// 1. 封装所有任务为Callable列表
List<Callable<String>> tasks = new ArrayList<>();
for (String orderId : orderIds) {
tasks.add(processOrderTask(orderId));
}
// 2. 批量提交任务:invokeAll等待所有任务完成,返回Future列表
List<Future<String>> futures = ORDER_THREAD_POOL.invokeAll(tasks);
// 3. 解析结果
List<String> results = new ArrayList<>();
for (Future<String> future : futures) {
if (!future.isCancelled()) {
results.add(future.get());
}
}
return results;
}
// 关闭所有线程池(JVM退出时调用)
public static void shutdownAllPools() {
shutdownPool(ORDER_THREAD_POOL, "订单线程池");
shutdownPool(LOG_THREAD_POOL, "日志线程池");
}
private static void shutdownPool(ThreadPoolExecutor pool, String name) {
pool.shutdown();
try {
if (!pool.awaitTermination(1, TimeUnit.MINUTES)) {
List<Runnable> unfinished = pool.shutdownNow();
System.out.println(name + "未完成任务数:" + unfinished.size());
}
System.out.println(name + "已关闭");
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
static void main() {
try {
long start = System.currentTimeMillis();
// 模拟批量处理100个订单
List<String> orderIds = new ArrayList<>();
for (int i = 0; i < 100; i++) {
orderIds.add("ORDER_" + i);
}
List<String> results = batchProcessOrders(orderIds);
System.out.println("===== 批量处理结果 =====");
results.forEach(System.out::println);
System.out.println("总耗时:" + (System.currentTimeMillis() - start) + "ms");
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
shutdownAllPools();
}
}
}

王二看着结果,算了算:“100 个订单,每个耗时 100ms,用 10 个核心线程,总耗时 1050ms,刚好是 10 个批次,效率太高了!”
“这就是 invokeAll 的好处,” 哇哥说,“批量提交任务,线程池自动分配线程处理,比你一个个 submit 高效多了。而且订单和日志用不同线程池,就算日志线程池满了,也不会影响订单处理。”
四、实战 2:线程池监控,提前发现问题

“生产环境里,线程池不能‘黑盒运行’,” 哇哥说,“必须监控它的状态,比如活跃线程数、队列任务数,一旦超过阈值就报警 —— 就像医院监控挂号人数,快满了就加临时窗口。”
✨ 线程池监控工具类(可直接集成到项目)

package cn.tcmeta.threadpoolexecutor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 线程池监控工具类
* @version: 1.0.0
*/
public class ThreadPoolMonitor {
private static final double ACTIVE_THREAD_THRESHOLD_RATIO = 0.8;
private static final int QUEUE_REMAINING_CAPACITY_ALARM_LIMIT = 10;
private static final long MONITOR_INTERVAL_SECONDS = 10L;
// 监控线程池状态,每隔10秒打印一次
public static void monitorThreadPool(ThreadPoolExecutor pool, String poolName) {
if (pool == null || poolName == null || poolName.isEmpty()) {
throw new IllegalArgumentException("线程池对象及名称不能为空");
}
Runnable monitorTask = () -> {
while (!pool.isShutdown() && !Thread.currentThread().isInterrupted()) {
try {
// 获取线程池状态信息
int corePoolSize = pool.getCorePoolSize();
int activeCount = pool.getActiveCount();
int maximumPoolSize = pool.getMaximumPoolSize();
long taskCount = pool.getTaskCount();
long completedTaskCount = pool.getCompletedTaskCount();
int queueSize = pool.getQueue() != null ? pool.getQueue().size() : 0;
int queueRemainingCapacity = -1;
if (pool.getQueue() instanceof ArrayBlockingQueue) {
queueRemainingCapacity = ((ArrayBlockingQueue<?>) pool.getQueue()).remainingCapacity();
}
// 打印监控信息
System.out.printf("[%s监控] 核心线程数:%d,活跃线程数:%d,最大线程数:%d," +
"总任务数:%d,已完成任务数:%d,队列任务数:%d,队列剩余容量:%d%n",
poolName, corePoolSize, activeCount, maximumPoolSize,
taskCount, completedTaskCount, queueSize, queueRemainingCapacity);
// 模拟报警逻辑:活跃线程数超过最大线程数的80%,或者队列剩余容量小于10
if (activeCount > maximumPoolSize * ACTIVE_THREAD_THRESHOLD_RATIO ||
(queueRemainingCapacity != -1 && queueRemainingCapacity < QUEUE_REMAINING_CAPACITY_ALARM_LIMIT)) {
System.out.println("【报警】" + poolName + "压力过大,请及时处理!");
}
TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS); // 每隔10秒监控一次
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception ex) {
// 忽略非中断异常,保证监控持续运行
}
}
};
Thread monitorThread = new Thread(monitorTask, "thread-pool-monitor-" + poolName);
monitorThread.setDaemon(true); // 设置为守护线程,随主线程退出而退出
monitorThread.start();
}
// 测试监控
static void main() throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),
r -> new Thread(r, "test-pool-")
);
try {
// 启动监控
monitorThreadPool(pool, "测试线程池");
// 提交100个任务,模拟压力
for (int i = 0; i < 100; i++) {
int taskId = i;
pool.submit(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread().getName() + ":处理任务" + taskId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 控制提交速率,避免瞬间提交过多任务
if (i % 5 == 0) {
TimeUnit.MILLISECONDS.sleep(50);
}
}
// 等待任务处理
TimeUnit.SECONDS.sleep(30);
} finally {
pool.shutdown(); // 确保无论如何都会尝试关闭线程池
}
}
}
- 执行结果
[测试线程池监控] 核心线程数:5,活跃线程数:0,最大线程数:10,总任务数:0,已完成任务数:0,队列任务数:0,队列剩余容量:20
...
[测试线程池监控] 核心线程数:5,活跃线程数:0,最大线程数:10,总任务数:100,已完成任务数:100,队列任务数:0,队列剩余容量:20
[测试线程池监控] 核心线程数:5,活跃线程数:0,最大线程数:10,总任务数:100,已完成任务数:100,队列任务数:0,队列剩余容量:20
[测试线程池监控] 核心线程数:5,活跃线程数:0,最大线程数:10,总任务数:100,已完成任务数:100,队列任务数:0,队列剩余容量:20
“有了这个监控,线程池压力大的时候,我们能提前发现,不用等系统崩了才救火,” 王二兴奋地说,“这就像给线程池装了个‘体温计’,一发烧就报警。”
五、面试必问:Executor 实战优化题(附答案)

哇哥整理了 3 道实战优化面试题,王二背完直呼 “稳了”:
🔔 面试题 1:如何实现线程池隔离?为什么要隔离?
答案:
- 实现方式:不同业务创建独立的 ThreadPoolExecutor,比如订单线程池、支付线程池、日志线程池,线程名、核心参数、队列、拒绝策略都单独配置。
- 隔离原因:避免 “一个业务故障拖垮整个系统”,比如日志线程池满了,不会影响订单处理;支付线程池出问题,不会影响商品查询。
✔️ 面试题 2:Executor 如何处理批量任务?invokeAll 和 invokeAny 有什么区别?
答案:
- 批量处理方式:用 ExecutorService 的 invokeAll 和 invokeAny 方法,封装多个 Callable 任务为列表提交。
区别:
- invokeAll:等待所有任务完成,返回 Future 列表,适合 “所有任务都要处理” 的场景(比如批量订单处理);
- invokeAny:等待任意一个任务完成,返回该任务的结果,适合 “取最快结果” 的场景(比如多渠道查询商品价格)。
😭 面试题 3:如何监控 ThreadPoolExecutor 的状态?核心监控指标有哪些?
监控方式:通过 ThreadPoolExecutor 的 API 获取状态信息,定时打印或集成到监控系统(比如 Prometheus+Grafana)。
核心监控指标:
- 活跃线程数(getActiveCount):反映线程池当前压力;
- 队列任务数(getQueue ().size ()):反映任务堆积情况;
- 总任务数 / 已完成任务数(getTaskCount/getCompletedTaskCount):反映线程池吞吐量;
- 核心线程数 / 最大线程数(getCorePoolSize/getMaximumPoolSize):确认配置是否合理。
总结:Executor 实战封神心法(王二刻在键盘上)

- Executors 是陷阱,自定义线程池才放心:核心参数自己配,有界队列防 OOM;
- 线程池要隔离,业务之间不扯皮:订单、支付、日志各用各的,故障影响小;
- 批量任务用 invoke,效率提升不用吹:invokeAll 等所有,invokeAny 取最快;
- 监控不能少,报警要趁早:活跃线程、队列任务数,异常及时处理;
- 用完线程池, shutdown 要记牢:平缓关闭加等待,避免任务丢了跑。
📌 哇哥的终极大招
“最后送你一句口诀,” 哇哥拍了拍王二的肩膀,“‘核心参数配得好,线程隔离少不了,批量处理用 invoke,监控报警要趁早’—— 把这句话记牢,生产环境用 Executor,保你不翻车。”
王二点了点头,把优化后的代码部署到测试环境,压测 1000 并发,线程池状态稳定,响应时间控制在 200ms 以内 —— 他终于不再是那个只会 new Thread 的菜鸟了。
关注我,下次咱们扒一扒 Executor 和 Spring 的结合用法 —— 怎么用 @Async 注解简化异步代码?怎么和 Spring 事务搭配?怎么用 Spring Boot 监控线程池?让你在 Spring 项目里把 Executor 用得炉火纯青,成为团队里的并发高手!



1925

被折叠的 条评论
为什么被折叠?



