分布式、高并发-Day02

以下是 Day 2 详细学习内容(线程池任务队列与拒绝策略实战,30 分钟完整计划),包含理论对比、分步代码实战和现象解析:

今日学习目标

  1. 握有界队列(ArrayBlockingQueue)与无界队列(LinkedBlockingQueue)的核心区别
  2. 理解 4 种拒绝策略的适用场景
  3. 实战:对比不同队列类型下线程池的行为差异

⏰ 时间分配

时间段 任务 详细内容
0-8 分钟 理论:队列类型与拒绝策略 1. 有界 vs 无界队列的内存安全问题 2. 4 种拒绝策略的使用场景对比 3. 生产环境队列容量设置原则
8-25 分钟 实战:双队列对比实验 1. 编写有界队列线程池(容量 1)2. 编写无界队列线程池 3. 提交过量任务,观察日志差异
25-30 分钟 总结与扩展 1. 记录两种队列的关键区别 2. 思考:为什么无界队列不会触发maximumPoolSize? 3. 扩展:如何选择合适的拒绝策略?

理论详解:队列类型与拒绝策略

  1. 有界队列 vs 无界队列
特性 有界队列(如ArrayBlockingQueue) 无界队列(如LinkedBlockingQueue)
队列容量 必须指定容量(如new ArrayBlockingQueue<>(100)) 容量默认Integer.MAX_VALUE(理论无限)
线程扩容 队列满后创建非核心线程(直到maximumPoolSize) 队列永不满,不会触发maximumPoolSize
内存风险 安全(容量可控) 可能 OOM(任务堆积导致内存溢出)
适用场景 流量可预测场景(如订单处理) 流量突发场景(如秒杀瞬时流量)
  1. 4 种拒绝策略对比
策略 类名 行为描述 适用场景
抛异常 AbortPolicy 直接抛出RejectedExecutionException 需要严格感知任务失败的场景
静默丢弃 DiscardPolicy 丢弃最新任务,无任何提示 非关键任务(如日志上报)
丢弃最老任务 DiscardOldestPolicy 丢弃队列中等待最久的任务,尝试处理新任务 希望处理最新任务的场景
调用者执行 CallerRunsPolicy 由提交任务的线程直接执行任务 减缓任务提交速度(保护线程池)

实战步骤:双队列对比实验

  • 实验 1:有界队列(触发拒绝策略)
import java.util.concurrent.*;

public class BoundedQueueDemo {
    public static void main(String[] args) {
        // 有界队列(容量1),最大线程2,拒绝策略抛异常
        ExecutorService pool = new ThreadPoolExecutor(
            1,                  // 核心线程1
            2,                  // 最大线程2(可扩展1个非核心线程)
            60,                 // 非核心线程存活60秒
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1),  // 队列容量1
            new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交3个任务:1核心线程+1队列+1非核心线程,第4个触发拒绝
        for (int i = 0; i < 4; i++) {
            int taskId = i;
            pool.execute(() -> {
                try {
                    System.out.println("任务" + taskId + "由" + Thread.currentThread().getName() + "处理");
                    Thread.sleep(2000);  // 模拟处理耗时2秒
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        pool.shutdown();
    }
}
  • 预期输出:
任务0由pool-1-thread-1处理  (核心线程)  
任务1进入队列  
任务2创建非核心线程pool-1-thread-2处理  
任务3提交时触发AbortPolicy,抛出RejectedExecutionException  

实验 2:无界队列(不触发拒绝策略)
java
public class UnboundedQueueDemo {
    public static void main(String[] args) {
        // 无界队列(默认容量无限),最大线程2(但队列不会满,故非核心线程不会创建)
        ExecutorService pool = new ThreadPoolExecutor(
            1,                  // 核心线程1
            2,                  // 最大线程2(无效,因队列无界)
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(),  // 无界队列
            new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交1000个任务(队列无限增长)
        for (int i = 0; i < 1000; i++) {
            int taskId = i;
            pool.execute(() -> {
                try {
                    System.out.println("任务" + taskId + "排队等待处理");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        pool.shutdown();
    }
}
  • 关键现象:
    • 仅核心线程pool-1-thread-1处理任务
    • 任务全部进入队列,不会创建非核心线程(因队列未填满)
    • 不会触发拒绝策略(队列永远有空间)

今日总结与扩展

  1. 核心对比表
实验项 有界队列(ArrayBlockingQueue) 无界队列(LinkedBlockingQueue)
队列满时 触发线程扩容(到 maxPoolSize) 不扩容(队列无限)
拒绝策略 可能触发(任务数 > maxPoolSize + 队列容量) 永不触发(队列不会满)
内存风险 低(容量可控) 高(可能堆积导致 OOM)
  1. 扩展思考(5 分钟)
    问题 1:生产环境为什么不建议用无界队列?
    答案:无界队列可能导致任务无限堆积,耗尽内存,典型案例:某电商活动因使用Executors.newFixedThreadPool(内部为无界队列)导致 OOM。
    问题 2:如何计算有界队列的合理容量?
    提示:根据任务处理耗时和预期并发量估算,例如:
    核心线程数 = 10,每个任务平均耗时 100ms
    队列容量 = 预期突发流量峰值 - 核心线程数 ×(超时时间 / 耗时)

工具与环境准备

  • 代码要求:直接复制上述两个 Java 文件,分别运行观察结果
  • JVM 参数:无需特殊配置,直接运行
  • 调试技巧:
    • 在pool.execute()前打印任务提交时间
    • 使用pool.getQueue().size()实时查看队列长度

✅ 今日任务 checklist

  • ✅ 理解有界队列与无界队列的核心区别
  • ✅ 成功运行两个实验,观察到拒绝策略触发与不触发的差异
  • ✅ 记录 1 个生产环境队列选择的思考(如:订单系统用有界队列,日志系统用无界队列)

你可能感兴趣的:(分布式,高并发,分布式)