Appearance
CHM 是什么?(ConcurrentHashMap 速览)
一句话定义
CHM = ConcurrentHashMap 的惯用缩写,是 Java 里为高并发设计的线程安全 Map:读多写多场景下用 桶级 CAS / 细粒度锁 代替整表一把锁,但只保证单次 API 原子,不替你保证 get 再 put 这类复合逻辑。
为什么线程安全、底层原理(JDK 8+):map-concurrency-compare §2.5
可运行对比:MapConcurrencyCompare.java
总览中的位置:Java 并发模型总览 §4
为什么要单独记这个名字
面试、代码评审、日志里常写「CHM 缓存」「换 CHM」——需要立刻对应到:
| 你说 | 实际指 |
|---|---|
| CHM | java.util.concurrent.ConcurrentHashMap |
| 线程安全 Map | 还可能指 Hashtable、Collections.synchronizedMap(粒度不同) |
| 并发字典 | 服务端 in-flight 表、本地索引、去重表 |
和「线程安全 HashMap」的区别
| 实现 | 锁粒度 | null | 典型误区 |
|---|---|---|---|
HashMap | 无 | 允许 null 键/值 | 多线程写会坏结构 |
Hashtable / synchronizedMap | 整表一把锁 | 常不允许 null | 并发高时吞吐差 |
ConcurrentHashMap(CHM) | 桶级 CAS + 必要时 synchronized 锁链表/树头 | 不允许 null 键/值 | 以为用了 CHM 就业务线程安全 |
口语:CHM 像「分格储物柜」,改不同格不用互相等;synchronizedMap 像「门口一个管理员」,谁都要排队。储物柜比喻与三条机制(volatile / CAS / 桶锁)见 lab §2.5.2~§2.5.4。
JDK 8+ CHM 长什么样(够用版)
与 HashMap 类似:Node[] table + 链表 / 红黑树(树化见 红黑树基础)。
| 操作 | 大致思路 |
|---|---|
读 get | 多数无锁读;配合 volatile 可见性 |
写 put | 空桶 CAS 占位;冲突桶对头节点或树加锁 |
| 扩容 | 多线程协助迁移,每桶只迁一次 |
| 复合 API | putIfAbsent、computeIfAbsent、merge 在桶内原子完成 |
JDK 7 是 Segment 分段锁;JDK 8 起改为与 HashMap 更接近的数组 + 桶锁,面试以 8+ 为准(对比表见 lab §2.5.6)。
能保证什么、不能保证什么
| ✅ 能保证 | ❌ 不能保证 |
|---|---|
单次 put / get / remove 等 API 内部一致性 | if (map.get(k) == null) map.put(k, v) 整体原子 |
computeIfAbsent 等同桶内「判断 + 写入」 | 多字段业务状态的一致性 |
弱一致迭代(一般不抛 ConcurrentModificationException) | size() 在极高并发下是近似快照 |
java
// ❌ 经典误用:两次 API,中间可被打断
if (map.get(key) == null) {
map.put(key, expensiveCreate());
}
// ✅ 方向:原子复合 API
map.computeIfAbsent(key, k -> expensiveCreate());Android / 工程里常见用法
| 场景 | 是否用 CHM | 备注 |
|---|---|---|
| 多线程 in-flight 请求去重(同 URL 共用一个 Call) | ✅ | 配 computeIfAbsent |
| 全局配置索引、多线程读写的注册表 | ✅ | 读多写少也常用 |
| 图片内存缓存 | ❌ 通常不用裸 CHM | 用 LruCache(见 LRU 基础) |
| UI 单线程访问的局部 Map | ❌ | HashMap 即可 |
和相近概念对照
| 概念 | 关系 |
|---|---|
| CAS 基础 | CHM 空桶插入、计数等用 CAS |
| 红黑树 | CHM 单桶与 HashMap 相同,可树化 |
ConcurrentSkipListMap | 有序并发 Map,O(log n),要排序时考虑 |
CopyOnWriteArrayList | 读多写极少时的 List,不是 Map |
复习检查题
CHM 是哪三个单词的缩写?和
synchronizedMap最大区别是什么?答:
ConcurrentHashMap。最大区别是并发粒度:CHM 桶级并发,synchronizedMap整表一把锁;高并发读写 CHM 吞吐通常更好。为什么 CHM 不允许 null 键和 null 值?
答:在并发语义下,
get(key)返回null无法区分「不存在」与「值为 null」,会增加containsKey判断和复合逻辑歧义,Doug Lea 设计时直接禁止 null 避免踩坑。用了 CHM 为什么
get-then-put仍可能重复创建对象?答:
get与put是两次独立操作,中间另一线程可能已插入。应使用putIfAbsent/computeIfAbsent在桶锁内完成检查+写入。面试说「CHM 线程安全」时,建议补哪半句?底层靠哪三条机制?
答:补「单次 API 调用线程安全;业务 check-then-act 要用原子 API 或锁」。底层:volatile 可见性 + 空桶 CAS + 非空桶头 synchronized(详见 lab §2.5.3)。
速记
- CHM = ConcurrentHashMap,Java 桶级并发,非整表锁。
- 不允许 null;复合逻辑用
computeIfAbsent。 - 原理深挖 → map-concurrency-compare §2.5;可运行 → MapConcurrencyCompare。