945 字
约 3 分钟
6
S05-J02 · 并发容器:CHM、阻塞队列、COW
适用读者:还在用
Collections.synchronizedMap的人。
前置:S05-B03、S04-J02(HashMap 源码)。
覆盖旧文:A-1588(247) A-1597(109) A-824(85) A-1591(70) + 835(85) 836(70) = 666 点。
覆盖扩展:E-S05-009(CHM 的 CAS+synchronized、COW 边界)。
上一篇:S05-J01 · AQS 与 ReentrantLock 源码
下一篇:S05-J03 · CompletableFuture。
1. 一句话结论
并发容器的演进只有一条主线:把"一把大锁"换成"更细的锁 + 无锁结构"。ConcurrentHashMap 是这条线的教科书。
| 容器 | 并发策略 |
|---|---|
Hashtable |
全表 synchronized(最慢) |
Collections.synchronizedMap |
全表锁(同上) |
| CHM(JDK 7) | 分段锁(Segment,默认 16) |
| CHM(JDK 8+) | CAS + 桶级 synchronized(锁单个桶头) |
CopyOnWriteArrayList |
写时复制(读写分离) |
2. 为什么 JDK 8 废弃分段锁(E-S05-009)
| 维度 | JDK 7 分段锁 | JDK 8 桶级锁 + CAS |
|---|---|---|
| 锁粒度 | 16 段(固定) | 每个桶头(更细) |
| 并发度 | 受段数限制(16) | 随桶数增长(可到容量级) |
| 空桶写入 | 需锁整段 | CAS 直接放(无锁) |
| 结构 | Segment[] → HashEntry[] | Node[] + 链表/红黑树(与 HashMap 同构) |
一句话:分段锁的"段数"成了上限,而桶级锁把并发度提到"桶数"——同时复用 HashMap 的红黑树优化。
2.1 三个关键机制
| 机制 | 作用 |
|---|---|
读操作不加锁(volatile 保证可见) |
高并发读几乎无开销 |
| 空桶插入用 CAS | 无竞争时无锁 |
| 桶非空时 synchronized 桶头 | 只锁一个桶 |
3. COW:写时复制(A-1591)
List<String> list = new CopyOnWriteArrayList<>();
list.add("x"); // 复制整个数组 + 加新元素 + 替换引用
| 特性 | 说明 |
|---|---|
| 读 | 完全无锁(读旧数组快照) |
| 写 | 复制整个数组(O(n))+ 加锁 |
| 内存 | 写时存在两份数组 |
| 迭代器 | 快照,不抛 CME |
判据:"读远多于写、且数据量不大?" 是 → COW;否则用 ArrayList + 外部锁或 ConcurrentLinkedQueue。
典型场景:配置列表、监听器列表(读多写极少)。
4. 阻塞队列:线程池的"缓冲区"(A-1597)
| 队列 | 结构 | 有界 | 典型用途 |
|---|---|---|---|
ArrayBlockingQueue |
数组 | ✅ 必须指定 | 线程池首选(防 OOM) |
LinkedBlockingQueue |
链表 | 可选(默认无界) | 注意默认无界的坑(B05) |
SynchronousQueue |
不存储 | 容量 0 | 直接交接(newCachedThreadPool) |
PriorityBlockingQueue |
堆 | 无界 | 优先级任务 |
DelayQueue |
延迟堆 | 无界 | 定时任务 |
S05-B05 的实测(core 2 + ArrayBlockingQueue(2) → 第 5 个被拒)就是"有界队列"的直接后果。
5. 五个误区
| 误区 | 真相 |
|---|---|
| "CHM 用一把锁" | JDK 8+ 用 CAS + 桶级锁 |
| "CHM 完全无锁" | 写冲突时仍会锁桶 |
| "COW 适合所有读多写少" | 写一次复制全数组,数据大时很贵 |
"LinkedBlockingQueue 有界" |
默认无界(Integer.MAX_VALUE) |
"synchronizedMap 够用了" |
全表锁,吞吐差 |
6. 自检
- CHM 在 JDK 7 与 8 的并发策略差异?为什么废弃分段锁?
- CHM 读操作为什么不加锁?
- COW 的适用边界是什么?
ArrayBlockingQueue与LinkedBlockingQueue的关键差别?SynchronousQueue的容量是多少?用在哪?
7. 下一篇
S05-J03 · 异步编排 CompletableFuture
容器讲完,下一篇讲"把异步任务串起来":实测两个 300ms 任务并行只要 361ms,allOf 两个 200ms 任务只要 204ms。
本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-1588/1597/824/1591/835/836;扩展点 E-S05-009。
S05-J02 · 并发容器:CHM、阻塞队列、COW
https://www.clxhxhhr.top/posts/5248/ 评论
0 条
还没有评论,先写一条吧。