Appearance
queue-strategy-compare
1. 实验目标与工程要点
本实验旨在对比 ArrayBlockingQueue、LinkedBlockingQueue 以及 SynchronousQueue 在 ThreadPoolExecutor 中的任务排队机制、核心/非核心线程扩容触发顺序以及对线程池并发吞吐量的影响。
2. 深入原理与生活浅显比喻
2.1 生活浅显比喻:三种不同的“候客等待区”
假设你开了一家餐馆,店里有 1 个正式厨师(核心线程),最多可加到 2 个厨师(最大线程):
ArrayBlockingQueue(2)(固定椅子等待区):- 门前摆放了 2 把固定椅子。
- 顾客 1 进门厨师 1 直接炒菜;顾客 2、3 进门坐在椅子上等;顾客 4 进门发现椅子满了,店家紧急雇佣厨师 2 炒菜;顾客 5 进门发现厨师全满、椅子也满,拒绝接单!
LinkedBlockingQueue(默认无界长龙等待区):- 门前有一条无限延伸的长廊(若未指定 capacity 默认为
Integer.MAX_VALUE)。 - 只要厨师 1 在忙,来再多顾客都会在长廊里无限排队。店里的厨师 2 永远不会被雇用,且如果顾客太多,长廊会挤爆(OOM 内存溢出)!
- 门前有一条无限延伸的长廊(若未指定 capacity 默认为
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. 常见误配与事故注意事项
⚠️ 使用默认
Executors.newFixedThreadPool(n)导致 OOM- 原因:
newFixedThreadPool默认使用了无界的LinkedBlockingQueue(容量为 21 亿)。 - 事故:当后台网络变慢或数据库卡顿时,生产速率远大于消费速率,几十万个任务积压在队列中,直接导致 Android / Java 进程发生
OutOfMemoryError。
- 原因:
⚠️ 使用
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. 对应知识库文档
- 理论主文档:Java 并发模型总览