Skip to content

queue-strategy-compare

1. 实验目标与工程要点

本实验旨在对比 ArrayBlockingQueueLinkedBlockingQueue 以及 SynchronousQueueThreadPoolExecutor 中的任务排队机制、核心/非核心线程扩容触发顺序以及对线程池并发吞吐量的影响。


2. 深入原理与生活浅显比喻

2.1 生活浅显比喻:三种不同的“候客等待区”

假设你开了一家餐馆,店里有 1 个正式厨师(核心线程),最多可加到 2 个厨师(最大线程):

  1. ArrayBlockingQueue(2)(固定椅子等待区)

    • 门前摆放了 2 把固定椅子
    • 顾客 1 进门厨师 1 直接炒菜;顾客 2、3 进门坐在椅子上等;顾客 4 进门发现椅子满了,店家紧急雇佣厨师 2 炒菜;顾客 5 进门发现厨师全满、椅子也满,拒绝接单!
  2. LinkedBlockingQueue(默认无界长龙等待区)

    • 门前有一条无限延伸的长廊(若未指定 capacity 默认为 Integer.MAX_VALUE)。
    • 只要厨师 1 在忙,来再多顾客都会在长廊里无限排队。店里的厨师 2 永远不会被雇用,且如果顾客太多,长廊会挤爆(OOM 内存溢出)!
  3. SynchronousQueue(无椅子直接手递手传递)

    • 门前 0 把椅子!顾客进门后不允许停留排队。
    • 顾客 1 进门厨师 1 直接炒菜;顾客 2 进门因为没有椅子坐,店家立即使唤厨师 2 炒菜;顾客 3 进门因为 2 个厨师都在忙且无椅子,直接拒绝接单!
    • 特点:响应极快,即来即做,适合短平快的任务处理。

2.2 三种阻塞队列对比矩阵

阻塞队列类型内部数据结构容量特征扩容触发条件吞吐性能运行开销适用 Android 业务场景
ArrayBlockingQueue数组 (Array)有界固定队列填满后,才开始创建非核心线程扩容🚀 较高(单锁存取,数组紧凑)📉 极低(内存预分配固定)图片解码/日志写入:需要硬性防止内存溢出的场景。
LinkedBlockingQueue链表 (Node)可指定容量,默认 Integer.MAX_VALUE(无界)若未限容量,非核心线程永远不会触发,任务无限堆积🚀 较高(存取双锁分离 takeLock/putLock📊 中等(频繁创建 Node 节点)后台推送/批量离线上传:允许任务暂存积压的异步队列。
SynchronousQueue无存储 (Zero-Capacity)0 容量任务提交立刻触发非核心线程创建(手递手传递)极高(直接交接,无缓存开销)📈 较高(频繁创建与销毁线程开销)网络请求/即时响应任务(如 Executors.newCachedThreadPool()):要求极低延迟响应。

3. 常见误配与事故注意事项

  1. ⚠️ 使用默认 Executors.newFixedThreadPool(n) 导致 OOM

    • 原因newFixedThreadPool 默认使用了无界的 LinkedBlockingQueue(容量为 21 亿)。
    • 事故:当后台网络变慢或数据库卡顿时,生产速率远大于消费速率,几十万个任务积压在队列中,直接导致 Android / Java 进程发生 OutOfMemoryError
  2. ⚠️ 使用 SynchronousQueue 配合 maximumPoolSize = Integer.MAX_VALUE 导致 CPU 爆满

    • 原因Executors.newCachedThreadPool() 使用了 SynchronousQueue
    • 事故:高并发突发请求下,因为没有队列缓冲,会瞬时创建上千个 OS 线程,导致 CPU 频繁上下文切换以及内存耗尽崩溃。

4. 实验源码与运行验证

关键源码

运行方式

bash
cd labs/java/runtime-concurrency/queue-strategy-compare
javac -d out src/QueueStrategyCompare.java
java -cp out QueueStrategyCompare

预期控制台运行输出

text
====== 1. ArrayBlockingQueue (有界有上限 capacity=2) ======
[ArrayBlockingQueue] 提交任务 1 成功 | 队列排队数: 0 | 活跃线程数: 1  (核心线程 1 启动)
[ArrayBlockingQueue] 提交任务 2 成功 | 队列排队数: 1 | 活跃线程数: 1  (入队 1/2)
[ArrayBlockingQueue] 提交任务 3 成功 | 队列排队数: 2 | 活跃线程数: 1  (入队 2/2,队列满)
[ArrayBlockingQueue] 提交任务 4 成功 | 队列排队数: 2 | 活跃线程数: 2  (触发创建非核心线程 2)
[ArrayBlockingQueue] 提交任务 5 失败 | 队列与线程池已满被拒绝            (4 个空间全满,任务 5 被拒绝)

====== 3. SynchronousQueue (零容量直接手递手传递) ======
[SynchronousQueue] 提交任务 1 成功 | 队列排队数: 0 | 活跃线程数: 1   (核心线程 1 启动)
[SynchronousQueue] 提交任务 2 成功 | 队列排队数: 0 | 活跃线程数: 2   (无法入队,直接创建非核心线程 2)
[SynchronousQueue] 提交任务 3 失败 | 队列与线程池已满被拒绝             (2 个线程全繁忙,无队列可入,任务 3 被拒绝)
[SynchronousQueue] 提交任务 4 失败 | 队列与线程池已满被拒绝
[SynchronousQueue] 提交任务 5 失败 | 队列与线程池已满被拒绝

5. 对应知识库文档