Skip to content

CHM 是什么?(ConcurrentHashMap 速览)

一句话定义

CHM = ConcurrentHashMap 的惯用缩写,是 Java 里为高并发设计的线程安全 Map:读多写多场景下用 桶级 CAS / 细粒度锁 代替整表一把锁,但只保证单次 API 原子,不替你保证 getput 这类复合逻辑。

为什么线程安全、底层原理(JDK 8+)map-concurrency-compare §2.5
可运行对比MapConcurrencyCompare.java
总览中的位置Java 并发模型总览 §4


为什么要单独记这个名字

面试、代码评审、日志里常写「CHM 缓存」「换 CHM」——需要立刻对应到:

你说实际指
CHMjava.util.concurrent.ConcurrentHashMap
线程安全 Map还可能指 HashtableCollections.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 占位;冲突桶对头节点或树加锁
扩容多线程协助迁移,每桶只迁一次
复合 APIputIfAbsentcomputeIfAbsentmerge 在桶内原子完成

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 等同桶内「判断 + 写入」多字段业务状态的一致性
弱一致迭代(一般不抛 ConcurrentModificationExceptionsize() 在极高并发下是近似快照
java
// ❌ 经典误用:两次 API,中间可被打断
if (map.get(key) == null) {
    map.put(key, expensiveCreate());
}

// ✅ 方向:原子复合 API
map.computeIfAbsent(key, k -> expensiveCreate());

Android / 工程里常见用法

场景是否用 CHM备注
多线程 in-flight 请求去重(同 URL 共用一个 Call)computeIfAbsent
全局配置索引、多线程读写的注册表读多写少也常用
图片内存缓存❌ 通常不用裸 CHMLruCache(见 LRU 基础
UI 单线程访问的局部 MapHashMap 即可

和相近概念对照

概念关系
CAS 基础CHM 空桶插入、计数等用 CAS
红黑树CHM 单桶与 HashMap 相同,可树化
ConcurrentSkipListMap有序并发 Map,O(log n),要排序时考虑
CopyOnWriteArrayList读多写极少时的 List,不是 Map

复习检查题

  1. CHM 是哪三个单词的缩写?和 synchronizedMap 最大区别是什么?

    ConcurrentHashMap。最大区别是并发粒度:CHM 桶级并发,synchronizedMap 整表一把锁;高并发读写 CHM 吞吐通常更好。

  2. 为什么 CHM 不允许 null 键和 null 值?

    :在并发语义下,get(key) 返回 null 无法区分「不存在」与「值为 null」,会增加 containsKey 判断和复合逻辑歧义,Doug Lea 设计时直接禁止 null 避免踩坑。

  3. 用了 CHM 为什么 get-then-put 仍可能重复创建对象?

    getput 是两次独立操作,中间另一线程可能已插入。应使用 putIfAbsent / computeIfAbsent 在桶锁内完成检查+写入。

  4. 面试说「CHM 线程安全」时,建议补哪半句?底层靠哪三条机制?

    :补「单次 API 调用线程安全;业务 check-then-act 要用原子 API 或锁」。底层:volatile 可见性 + 空桶 CAS + 非空桶头 synchronized(详见 lab §2.5.3)。

速记

  • CHM = ConcurrentHashMap,Java 桶级并发,非整表锁。
  • 不允许 null;复合逻辑用 computeIfAbsent
  • 原理深挖 → map-concurrency-compare §2.5;可运行 → MapConcurrencyCompare。