★ ConcurrentHashMap 1.7 与 1.8 的实现有何区别?
结论先行:1.7 用分段锁:内部是 Segment 数组,每段是一把 ReentrantLock,锁粒度是“段”;1.8 改为 CAS + synchronized 锁桶头节点,锁粒度细到单个桶,并引入红黑树优化长链,彻底废弃 Segment。1.8 并发度更高、内存更省,读操作基本无锁。
版本对比
| 维度 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 数据结构 | Segment + HashEntry 数组 | Node 数组 + 链表 + 红黑树 |
| 加锁方式 | 每段一把锁(锁段) | CAS 定位 + synchronized 锁桶头 |
| 并发粒度 | 默认 16 个 Segment | 单桶,容量多大并发度多大 |
| 扩容 | 段内自行扩容 | 支持多线程协助扩容 |
| 计数 | 累加各段再校验重试 | CounterCell 分散计数 |
| 查询 | 也要经过 Segment | 无锁读(volatile 保证可见性) |
1.8 写入流程速记
- 桶位为空 → 直接 CAS 把新节点放进去,不加锁。
- 桶位已有节点 → synchronized 锁住该桶头节点后插入链表或树。
- 链表过长且数组容量 ≥ 64 → 转红黑树优化查询。
- 读与遍历不设锁,靠 volatile 修饰 Node 的 next/val 保证可见性,弱一致迭代不抛 ConcurrentModificationException。
记忆点:写锁桶头、读不设防——1.8 的并发策略可以浓缩成这八个字。
import java.util.concurrent.ConcurrentHashMap;
public class CHMDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 单个 API 原子,复合逻辑仍需手动保证,例如:
map.compute("count", (k, v) -> v == null ? 1 : v + 1); // 原子自增
map.putIfAbsent("init", 0); // 存在才放
System.out.println(map);
}
}
常见追问 / 记忆点
- 记忆点:1.7 锁段、1.8 锁桶 + CAS,Segment 已是历史名词。
- 追问:1.8 为何用 synchronized 而不用 ReentrantLock——锁升级成熟后 synchronized 性能不输,且少一层对象、代码更简。
- 追问:key 与 value 都禁止为 null:并发下无法区分“没有该 key”与“value 就是 null”这两种状态。