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. 资源消耗问题:避免频繁创建销毁线程

  2. 稳定性问题:控制并发数量,防止系统崩溃

  3. 管理问题:统一任务调度和资源分配,防止线程竞争

  4. 为什么说频繁创建销毁线程消耗资源

  5. 每个线程都要占据独立的内存(比如栈空间),比如一个线程的栈空间,默认大小是1~8MB(java是1MB),那么1000个线程就要1GB,还没算线程控制块、寄存器状态等其它开销

  6. 创建线程得让系统内核亲自操作

  7. 线程过多,会让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); } }

🎯 本章总结与主动回忆检查

🔴 核心必会知识点

  1. 线程池七大参数的含义和配置原则
  2. 工作流程:核心线程 → 队列 → 临时线程 → 拒绝策略
  3. 四种拒绝策略的区别和适用场景
  4. 为什么不能用Executors创建线程池

🧠 主动回忆测试

请合上笔记,回答以下问题

  1. 场景题:corePoolSize=3, maximumPoolSize=10, queueCapacity=5时:
  • 第1-3个任务来了会怎样?
  • 第4-8个任务来了会怎样?
  • 第9-13个任务来了会怎样?
  • 第14个任务来了会怎样?
  1. 面试题:为什么推荐使用CallerRunsPolicy作为拒绝策略?
  2. 调优题:发现队列经常满,但CPU使用率不高,应该调整什么参数?

💡 参考答案

  1. 场景题答案
  • 第1-3任务:核心厨师立即处理
  • 第4-8任务:进入队列等待
  • 第9-13任务:雇佣临时厨师处理
  • 第14任务:执行拒绝策略
  1. 面试题答案:CallerRunsPolicy让调用线程执行任务,既不会丢失任务,又能给线程池喘息时间,是最温和的拒绝策略。
  2. 调优题答案:可能任务执行有I/O阻塞,可以适当增加核心线程数,或者使用更大的队列。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
面向对象的王者很迟缓
下载 APP