大家好呀! 今天咱们来聊聊Java线程池这个"厨房管理大师",保证让你看完后连家里买菜的大妈都能明白线程池是咋回事! 这篇文章会很长很长(超过3000字哦),但我会用最生活化的例子让你轻松掌握这个Java高并发核心知识点!
想象一下你开了一家餐馆,每次来一个客人你就新雇一个厨师,客人走了就把厨师开除…这得多折腾啊! 线程池就是帮你管理这些"厨师"的智能管家,它会让固定数量的厨师一直待命,来活了就分配,没活了就歇着,既高效又省钱!
// 原始做法:来一个任务创建一个线程(就像来一个客人雇一个厨师)
new Thread(new Runnable() {
@Override
public void run() {
// 处理任务
}
}).start();
这种方式的三大致命问题:
线程池的核心思想:复用线程,就像固定几个厨师轮流服务所有客人
主要优点:
让我们用餐馆后厨来类比线程池的组成:
线程池组件 | 厨房对应 | 作用说明 |
---|---|---|
核心厨师 | corePoolSize | 餐馆必备的常驻厨师,即使没客人也不会被解雇 |
最大厨师数 | maximumPoolSize | 餐馆最多能雇佣的厨师数量(包括核心厨师) |
厨房任务 | workQueue | 客人点的菜单队列,厨师按照顺序处理 |
临时工 ⏳ | 非核心线程 | 高峰期临时雇佣的厨师,空闲一段时间后会被解雇 |
厨师长 ️♂️ | ThreadFactory | 负责招聘厨师(创建线程) |
解雇策略 | RejectedExecutionHandler | 当餐馆爆满时的处理策略:拒绝客人、赶走老客人等 |
Java中创建线程池主要通过ThreadPoolExecutor
类:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
// 例子:餐馆有2个正式厨师,最多能雇佣5个厨师
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
// 其他参数...
);
这是存放待处理任务的"菜单",常见类型:
队列类型 | 特点 | 适用场景 |
---|---|---|
直接提交队列(SynchronousQueue) | 容量为0,来一个任务必须立刻处理,否则就创建新线程 | 快速响应的短期任务 |
有界队列(ArrayBlockingQueue) | 固定大小的队列,满了才会创建新线程 | 需要控制资源消耗的场景 |
无界队列(LinkedBlockingQueue) | 理论上无限大的队列,可能导致OOM | 任务处理速度较慢但稳定的场景 |
优先级队列(PriorityBlockingQueue) | 按优先级处理任务 | 需要区分任务优先级的场景 |
当餐馆爆满(队列满+线程数达上限)时的处理策略:
策略名称 | 行为 | 生活比喻 |
---|---|---|
AbortPolicy(默认) | 直接抛出RejectedExecutionException异常 | “客满啦,新客人请离开!” |
CallerRunsPolicy | 让提交任务的线程自己执行该任务 | “老板亲自下厨!” |
DiscardPolicy | 默默丢弃无法处理的任务 | “假装没看见新客人” |
DiscardOldestPolicy | 丢弃队列中最旧的任务,然后重试 | “让等最久的客人离开,接待新客人” |
Java提供了四种现成的"餐馆经营模式":
固定大小餐馆 (FixedThreadPool)
Executors.newFixedThreadPool(5); // 5个固定厨师
单线程餐馆 (SingleThreadExecutor)
Executors.newSingleThreadExecutor(); // 只有1个厨师
弹性餐馆 (CachedThreadPool)
Executors.newCachedThreadPool(); // 厨师数量弹性变化
定时餐馆 (ScheduledThreadPool)
Executors.newScheduledThreadPool(3); // 3个厨师处理定时任务
⚠️ 注意:实际开发中建议手动创建ThreadPoolExecutor
,因为预定义线程池的参数可能不适合生产环境!
线程池的工作流程就像餐馆的运营流程,让我们通过一个完整例子来理解:
假设我们有一个这样的线程池:
1. 来1个客人 ➡️ 分配给核心厨师1
[做菜1] [空闲]
2. 来第2个客人 ➡️ 分配给核心厨师2
[做菜1] [做菜2]
3. 来第3个客人 ➡️ 菜单队列加入第1道待做菜
[做菜1] [做菜2] | [菜3等待]
4. 来第4、5个客人 ➡️ 菜单队列满 (3道等待)
[做菜1] [做菜2] | [菜3,4,5等待]
5. 来第6个客人 ➡️ 雇佣临时工1做菜6
[做菜1] [做菜2] [做菜6] | [菜3,4,5等待]
6. 来第7个客人 ➡️ 雇佣临时工2做菜7
[做菜1] [做菜2] [做菜6] [做菜7] | [菜3,4,5等待]
7. 来第8个客人 ➡️ 餐馆爆满!触发拒绝策略
ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
4, // 核心线程4个
8, // 最大线程8个
30, // 临时工空闲30秒
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列容量100
new CustomThreadFactory(), // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 调用者运行策略
);
// 自定义线程工厂示例
class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger counter = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "CustomThread-" + counter.getAndIncrement());
thread.setPriority(Thread.NORM_PRIORITY);
thread.setDaemon(false);
return thread;
}
}
// 1. execute - 没有返回值
executor.execute(() -> System.out.println("执行任务"));
// 2. submit - 返回Future
Future future = executor.submit(() -> {
System.out.println("有返回值的任务");
return "结果";
});
// 3. invokeAny - 任意一个任务完成就返回
String result = executor.invokeAny(Arrays.asList(
() -> "任务1结果",
() -> "任务2结果"
));
// 4. invokeAll - 执行所有任务并返回Future列表
List> futures = executor.invokeAll(Arrays.asList(
() -> "任务1结果",
() -> "任务2结果"
));
// 获取线程池实时数据
int poolSize = executor.getPoolSize(); // 当前线程数
int activeCount = executor.getActiveCount(); // 活动线程数
long completedCount = executor.getCompletedTaskCount(); // 已完成任务数
int queueSize = executor.getQueue().size(); // 队列中任务数
// 扩展ThreadPoolExecutor实现监控
class MonitorThreadPoolExecutor extends ThreadPoolExecutor {
// 记录最大线程数
private int maxActiveThreads;
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
int activeCount = getActiveCount();
if (activeCount > maxActiveThreads) {
maxActiveThreads = activeCount;
}
System.out.println("任务开始执行,当前活跃线程: " + activeCount);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("任务执行完成,当前活跃线程: " + getActiveCount());
}
}
CPU密集型任务 (如计算、加密):
int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
IO密集型任务 (如网络请求、数据库操作):
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
混合型任务:
// 根据业务比例调整
int corePoolSize = (int)(Runtime.getRuntime().availableProcessors() / (1 - 阻塞系数));
// 阻塞系数 = 任务等待时间 / 任务总耗时
任务堆积OOM
线程泄漏 ️
死锁风险 ☠️
资源浪费
动态调整参数 ⚡
executor.setCorePoolSize(newSize); // 动态调整核心线程数
executor.setMaximumPoolSize(newMaxSize); // 动态调整最大线程数
预热核心线程
executor.prestartAllCoreThreads(); // 提前启动所有核心线程
合理关闭线程池
executor.shutdown(); // 温和关闭,处理完队列任务
executor.shutdownNow(); // 立即关闭,返回未执行任务列表
使用ForkJoinPool
对于可分治的任务,使用Java7引入的Fork/Join框架:
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
forkJoinPool.invoke(new RecursiveTask());
// 秒杀线程池配置
ThreadPoolExecutor seckillExecutor = new ThreadPoolExecutor(
20, // 核心20线程应对日常
200, // 最大200线程应对秒杀
60, // 空闲60秒回收
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5000), // 队列容量5000
new NamedThreadFactory("seckill-pool"), // 命名线程
new SeckillRejectedPolicy() // 自定义拒绝策略
);
// 自定义拒绝策略:返回秒杀失败提示
class SeckillRejectedPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (r instanceof SeckillTask) {
((SeckillTask) r).getUser().sendMsg("当前参与人数过多,请稍后再试");
}
}
}
// 文件处理线程池
ThreadPoolExecutor fileExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(), // 核心线程=CPU核数
Runtime.getRuntime().availableProcessors() * 2, // 最大线程=CPU核数*2
30, // 空闲30秒回收
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // 有界队列防止OOM
new NamedThreadFactory("file-processor"),
new ThreadPoolExecutor.CallerRunsPolicy() // 调用者运行避免丢失任务
);
// 处理目录下所有文件
public void processDirectory(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
fileExecutor.submit(() -> processFile(file));
}
}
}
private void processFile(File file) {
// 文件处理逻辑...
}
Java 8引入的CompletableFuture内部使用ForkJoinPool:
// 异步执行任务
CompletableFuture.supplyAsync(() -> {
// 耗时操作
return "结果";
}, executor); // 可以指定自定义线程池
// 链式调用
CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(s -> s + " World") // 异步转换
.thenAcceptAsync(System.out::println); // 异步消费
底层同样使用ForkJoinPool:
List results = dataList.parallelStream()
.filter(item -> item.startsWith("A"))
.map(String::toUpperCase)
.collect(Collectors.toList());
⚠️ 注意:默认使用公共的ForkJoinPool,大量任务时建议自定义线程池:
ForkJoinPool customPool = new ForkJoinPool(4);
customPool.submit(() ->
dataList.parallelStream()
// 处理逻辑...
).get();
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
什么是 Cookie?简单介绍与使用方法
什么是 Session?如何应用?
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
如何理解应用 Java 多线程与并发编程?
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
Java Spring 中常用的 @PostConstruct 注解使用总结
如何理解线程安全这个概念?
理解 Java 桥接方法
Spring 整合嵌入式 Tomcat 容器
Tomcat 如何加载 SpringMVC 组件
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
Java 中消除 If-else 技巧总结
线程池的核心参数配置(仅供参考)
【人工智能】聊聊Transformer,深度学习的一股清流(13)
Java 枚举的几个常用技巧,你可以试着用用
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
HTTP、HTTPS、Cookie 和 Session 之间的关系
使用 Spring 框架构建 MVC 应用程序:初学者教程
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
Java Spring 中常用的 @PostConstruct 注解使用总结
线程 vs 虚拟线程:深入理解及区别
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)