JUC-线程池
第一章:线程池——厨房的厨师团队管理系统
1.1 为什么需要线程池?——从"临时工"到"专业团队"的进化
🍳 厨房场景引入
想象一下你的"Java餐厅"生意火爆,但管理方式还很原始:
原始做法:来一个订单雇一个新厨师
▼java复制代码// 糟糕的厨房管理 - 每次订单都新雇厨师 public class NaiveKitchen { public void processOrder(Order order) { // 来一个订单就新雇一个厨师,做完就开除! new Thread(() -> { System.out.println("新厨师上岗..."); cookOrder(order); System.out.println("厨师下班..."); }).start(); } }
问题分析(问题解决思维链):
▼plain复制代码现象:厨房越来越卡,订单响应变慢 可能原因: ✅ 频繁招聘解雇厨师,开销巨大(线程创建销毁成本) ✅ 同时100个订单就雇100个厨师,厨房挤爆(资源耗尽) ✅ 厨师间缺乏协调,经常撞车(线程竞争) 排查命令:jstack查看线程数,top查看资源使用 解决方案:建立稳定的厨师团队(线程池)
🔴 核心必会:线程池解决的三大问题
-
资源消耗问题:避免频繁创建销毁线程
-
稳定性问题:控制并发数量,防止系统崩溃
-
管理问题:统一任务调度和资源分配,防止线程竞争
-
为什么说频繁创建销毁线程消耗资源
-
每个线程都要占据独立的内存(比如栈空间),比如一个线程的栈空间,默认大小是1~8MB(java是1MB),那么1000个线程就要1GB,还没算线程控制块、寄存器状态等其它开销
-
创建线程得让系统内核亲自操作
-
线程过多,会让CPU频繁切换任务,无法正常工作
1.2 线程池核心架构——智能厨师团队设计
🏗️ 知识联系图
▼plain复制代码线程池创建 → 理解七大参数 → 掌握工作流程 → 选择拒绝策略 ↓ 任务提交 → 核心线程处理 → 队列缓冲 → 临时线程扩容
📊 七大参数对比学习表
| 参数 | 厨房类比 | 作用 | 错误配置后果 |
|---|---|---|---|
| corePoolSize | 核心厨师编制 | 长期保留的线程数 | 设置过小:旺季忙不过来 |
| maximumPoolSize | 最大厨师编制 | 线程数上限 | 设置过大:厨房挤爆,资源OOM |
| keepAliveTime | 临时工空闲时间 | 非核心线程存活时间 | 时间不合理:频繁招聘解雇 |
| TimeUnit | 时间单位 | 存活时间单位 | - |
| BlockingQueue | 订单等待区 | 任务队列 | LinkedBlockingQueue无界队列很危险! |
| ThreadFactory | 厨师招聘标准 | 线程创建工厂 | 不设名字:出问题找不到人 |
| RejectedExecutionHandler | 爆单处理策略 | 拒绝策略 | 不了解策略区别:订单丢失或系统崩溃 |
💻 代码实战:创建智能厨房线程池
▼java复制代码/** * 智能厨房管理系统 - 线程池最佳实践 */ public class SmartKitchen { // 🎯 核心必会:正确的线程池创建方式 private final ThreadPoolExecutor chefTeam; public SmartKitchen() { this.chefTeam = new ThreadPoolExecutor( // 1. 核心团队:5名核心厨师(长期雇佣) 5, // 2. 最大编制:旺季最多10名厨师(含核心) 10, // 3. 临时工政策:空闲30秒后解雇 30, TimeUnit.SECONDS, // 4. 订单等待区:最多积压50个订单(有界队列!) new ArrayBlockingQueue<>(50), // 5. 厨师招聘标准:规范化管理 new KitchenThreadFactory(), // 6. 爆单处理:让下单客人稍等(最合理的策略) new ThreadPoolExecutor.CallerRunsPolicy() ); } /** * 🎯 主动回忆检查点: * 思考:为什么使用ArrayBlockingQueue而不是LinkedBlockingQueue? * 答案:ArrayBlockingQueue是有界队列,防止订单无限堆积导致内存溢出 */ } /** * 自定义线程工厂 - 给厨师起名字,便于监控 */ class KitchenThreadFactory implements ThreadFactory { private final AtomicInteger chefCounter = new AtomicInteger(1); @Override public Thread newThread(Runnable task) { Thread chef = new Thread(task, "厨房厨师-" + chefCounter.getAndIncrement()); // 设置异常处理器 - 厨师出问题时的应急预案 chef.setUncaughtExceptionHandler((thread, throwable) -> { System.err.println("厨师 " + thread.getName() + " 工作中出现问题: " + throwable.getMessage()); // 可以在这里记录日志、发送告警等 }); return chef; } }
1.3 线程池工作流程——订单处理全链路
🍽️ 厨房工作流程图解
▼plain复制代码订单到达 ↓ 是否有空闲核心厨师? → 有 → 立即开始烹饪 ↓ 没有 订单等待区是否已满? → 未满 → 进入等待区排队 ↓ 已满 是否还能雇临时厨师? → 能 → 雇佣临时厨师处理 ↓ 不能 执行拒绝策略
💻 代码演示:完整的订单处理系统
▼java复制代码public class OrderProcessingSystem { private final ThreadPoolExecutor kitchen; // 线程池 private final AtomicLong orderIdGenerator = new AtomicLong(1); public OrderProcessingSystem() { this.kitchen = new ThreadPoolExecutor( 3, 8, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20), new KitchenThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); } /** * 🎯 核心必会:提交订单到线程池 */ public Future<String> submitOrder(String dishName, int cookingTime) { return kitchen.submit(() -> { long orderId = orderIdGenerator.getAndIncrement(); System.out.println("🎯 订单" + orderId + "[" + dishName + "] - 厨师: " + Thread.currentThread().getName() + " 开始制作..."); // 模拟烹饪过程 Thread.sleep(cookingTime); String result = "订单" + orderId + "[" + dishName + "] 制作完成!"; System.out.println("✅ " + result); return result; }); } /** * 🎯 进阶理解:监控厨房运营状态 */ public void printKitchenStatus() { System.out.println("\n=== 厨房运营报表 ==="); System.out.println("核心厨师数: " + kitchen.getCorePoolSize()); System.out.println("当前厨师数: " + kitchen.getPoolSize()); System.out.println("活跃厨师数: " + kitchen.getActiveCount()); System.out.println("已完成订单: " + kitchen.getCompletedTaskCount()); System.out.println("等待订单数: " + kitchen.getQueue().size()); System.out.println("总订单数: " + kitchen.getTaskCount()); } /** * 🎯 问题解决思维链:模拟高并发场景测试 */ public void stressTest() throws InterruptedException { System.out.println("🚀 开始压力测试:模拟50个订单同时到达..."); List<Future<String>> futures = new ArrayList<>(); // 模拟50个订单同时到达 for (int i = 1; i <= 50; i++) { final int orderNum = i; Future<String> future = kitchen.submit(() -> { // 模拟不同的烹饪时间 int cookTime = 1000 + (int)(Math.random() * 2000); Thread.sleep(cookTime); return "订单" + orderNum + " 完成,耗时: " + cookTime + "ms"; }); futures.add(future); } // 等待所有订单完成 for (Future<String> future : futures) { try { System.out.println(future.get()); } catch (ExecutionException e) { System.err.println("订单处理失败: " + e.getCause().getMessage()); } } printKitchenStatus(); } }
1.4 四种拒绝策略——爆单时的应急预案
📋 拒绝策略对比学习表
| 策略 | 厨房类比 | 行为 | 适用场景 | 风险 |
|---|---|---|---|---|
| AbortPolicy | 直接拒单 | 抛出RejectedExecutionException | 严格要求不丢失任务 | 业务中断 |
| CallerRunsPolicy | 让客人自己动手 | 在调用者线程执行任务 | Web应用推荐 | 可能阻塞主线程 |
| DiscardPolicy | 默默拒单 | 直接丢弃,无通知 | 可容忍任务丢失 | 数据丢失 |
| DiscardOldestPolicy | 扔掉最旧订单 | 丢弃队列头任务,重试提交 | 重视最新任务 | 重要任务可能被丢弃 |
💻 代码演示:不同的拒绝策略效果
▼java复制代码public class RejectionPolicyDemo { public static void main(String[] args) { System.out.println("🧪 测试不同拒绝策略的效果..."); // 测试1:AbortPolicy - 直接拒单抛异常 testPolicy("AbortPolicy", new ThreadPoolExecutor.AbortPolicy()); // 测试2:CallerRunsPolicy - 客人自己动手 testPolicy("CallerRunsPolicy", new ThreadPoolExecutor.CallerRunsPolicy()); } private static void testPolicy(String policyName, RejectedExecutionHandler policy) { System.out.println("\n=== 测试策略: " + policyName + " ==="); ThreadPoolExecutor kitchen = new ThreadPoolExecutor( 2, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), // 很小的队列,容易触发拒绝 policy ); try { // 提交6个任务,但线程池最多处理4个(2个执行 + 2个排队) for (int i = 1; i <= 6; i++) { final int taskId = i; try { kitchen.execute(() -> { try { Thread.sleep(1000); System.out.println("任务" + taskId + " 完成"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); System.out.println("任务" + taskId + " 提交成功"); } catch (Exception e) { System.out.println("❌ 任务" + taskId + " 被拒绝: " + e.getClass().getSimpleName()); } } } finally { kitchen.shutdown(); } } }
1.5 预定义线程池——快餐式模板的危险
⚠️ 核心必会:为什么不能用Executors?
▼java复制代码public class ExecutorsDangerDemo { /** * 🚨 危险做法1:无界队列导致内存溢出 */ public static void danger1() { // 等待队列无限大,任务无限堆积,最终OOM! ExecutorService executor = Executors.newFixedThreadPool(10); // 等效于:new ThreadPoolExecutor(10, 10, 0L, MILLISECONDS, new LinkedBlockingQueue<>()); } /** * 🚨 危险做法2:无限制创建线程导致资源耗尽 */ public static void danger2() { // 最大线程数=Integer.MAX_VALUE,来多少任务创建多少线程! ExecutorService executor = Executors.newCachedThreadPool(); // 等效于:new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, SECONDS, new SynchronousQueue<>()); } /** * ✅ 安全做法:手动创建,控制所有参数 */ public static ThreadPoolExecutor safeSolution() { return new ThreadPoolExecutor( 5, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), // 有界队列,安全! new KitchenThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); } }
🎯 面试金句:
"阿里开发规范禁止使用Executors创建线程池,因为其预设参数不合理。FixedThreadPool和SingleThreadPool使用无界队列可能堆积请求导致OOM,CachedThreadPool最大线程数无限制可能创建过多线程导致OOM。应该使用ThreadPoolExecutor根据业务场景定制参数。"
1.6 监控与调优——厨房运营数据分析
🔧 问题解决思维链:性能调优实战
现象:订单处理慢,客户投诉增多
排查流程:
▼java复制代码public class KitchenMonitor { private final ThreadPoolExecutor kitchen; public KitchenMonitor(ThreadPoolExecutor kitchen) { this.kitchen = kitchen; } /** * 🎯 核心必会:线程池健康检查 */ public void healthCheck() { System.out.println("\n📊 厨房健康检查报告:"); System.out.println("核心厨师利用率: " + getCoreUtilization() + "%"); System.out.println("队列饱和度: " + getQueueSaturation() + "%"); System.out.println("最大容量利用率: " + getMaxPoolUtilization() + "%"); // 给出调优建议 giveOptimizationAdvice(); } private double getCoreUtilization() { return (double) kitchen.getActiveCount() / kitchen.getCorePoolSize() * 100; } private double getQueueSaturation() { return (double) kitchen.getQueue().size() / kitchen.getQueue().remainingCapacity() * 100; } private double getMaxPoolUtilization() { return (double) kitchen.getPoolSize() / kitchen.getMaximumPoolSize() * 100; } private void giveOptimizationAdvice() { double queueSat = getQueueSaturation(); double coreUtil = getCoreUtilization(); if (queueSat > 80 && coreUtil > 90) { System.out.println("💡 调优建议:增加核心线程数或最大线程数"); } else if (queueSat > 80 && coreUtil < 60) { System.out.println("💡 调优建议:任务执行较慢,检查任务逻辑或I/O阻塞"); } else if (queueSat < 20 && coreUtil < 50) { System.out.println("💡 调优建议:可适当减少核心线程数节约资源"); } } /** * 🎯 进阶理解:动态调整线程池参数 */ public void dynamicAdjust(int newCoreSize, int newMaxSize) { kitchen.setCorePoolSize(newCoreSize); kitchen.setMaximumPoolSize(newMaxSize); System.out.println("🔄 线程池参数已调整: core=" + newCoreSize + ", max=" + newMaxSize); } }
🎯 本章总结与主动回忆检查
🔴 核心必会知识点
- 线程池七大参数的含义和配置原则
- 工作流程:核心线程 → 队列 → 临时线程 → 拒绝策略
- 四种拒绝策略的区别和适用场景
- 为什么不能用Executors创建线程池
🧠 主动回忆测试
请合上笔记,回答以下问题:
- 场景题:corePoolSize=3, maximumPoolSize=10, queueCapacity=5时:
- 第1-3个任务来了会怎样?
- 第4-8个任务来了会怎样?
- 第9-13个任务来了会怎样?
- 第14个任务来了会怎样?
- 面试题:为什么推荐使用CallerRunsPolicy作为拒绝策略?
- 调优题:发现队列经常满,但CPU使用率不高,应该调整什么参数?
💡 参考答案
- 场景题答案:
- 第1-3任务:核心厨师立即处理
- 第4-8任务:进入队列等待
- 第9-13任务:雇佣临时厨师处理
- 第14任务:执行拒绝策略
- 面试题答案:CallerRunsPolicy让调用线程执行任务,既不会丢失任务,又能给线程池喘息时间,是最温和的拒绝策略。
- 调优题答案:可能任务执行有I/O阻塞,可以适当增加核心线程数,或者使用更大的队列。
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
内容推荐
#字节内推# 实习、校招、社招均有岗位
4
Day 68时间19:00~ 22:00(3h)✅ 今天做了:Component注解、Mybatis配置、使用⏰ 明天计划:Lombok、Mapper映射、动态SQL📚 今日感悟:自动配置类DataSourceAutoConfiguration ,会读取properties文件,通过注解:@EnableConfigurationProperties(DataSourceProperties.cl
1
Day 19✅ 今天做了:MCP⏰ 明天计划:AI智能体构建📚 今日感悟:今天MCP问题有点多有点杂,明天找时间再捋一下。继续加油
0
Day 25✅ 今天做了:1、扇贝英语单词打卡2、英语听说读写、听力练习3、微信阅读15分钟4、编程导航学习⏰ 明天计划:待定📚 今日感悟:Keep going!
1
Day 104✅ 今天做了:学习了Java反射及快速入门⏰ 明天计划:继续学习Java反射
0
