concurrenthashmap通过分段锁(jdk7)或cas+桶级锁(jdk8+)实现线程安全,读不加锁、写只锁单个桶,支持高并发;不允许null键或值,而hashmap允许、hashtable完全禁止;提供computeifabsent等原子方法替代手动同步,迭代器弱一致、不抛concurrentmodificationexception。

直接用 ConcurrentHashMap 替换 Hashtable 或 Collections.synchronizedMap(new HashMap()),关键不是“改代码”,而是理解它怎么靠局部锁机制避免全局阻塞——读不加锁、写只锁单个桶,自然就平滑了。
先确认哪些地方不能照搬
ConcurrentHashMap 不允许 null 键或值,而 Hashtable 和同步 HashMap 是允许的。如果旧逻辑里有类似 map.put("key", null) 或依赖 get(key) == null 判断缺失,得提前处理:
- 把
null值统一替换成Optional.empty()、哨兵对象(如MISSING_VALUE)或直接抛异常明确语义 - 检查
get()返回null的场景,区分是“键不存在”还是“值为 null”,改用containsKey()+get()组合判断 - 避免在初始化时传入含
null的 Map,例如new ConcurrentHashMap(otherMap)前需过滤
用原子方法替代手动同步的复合操作
全局锁容器常被误用于“检查再插入”这类非原子操作,比如:
if (!map.containsKey(key)) { map.put(key, value); }
这段代码即使在 Hashtable 中也不线程安全。ConcurrentHashMap 提供原生支持:
- 用
computeIfAbsent(key, k -> value)替代“查无则设”,整个过程原子执行 - 用
merge(key, value, (old, new) -> newValue)替代“存在则更新,否则插入” - 用
putIfAbsent(key, value)替代简单版“仅首次插入”
这些方法内部基于 CAS 或桶级锁完成,无需额外 synchronized 块,也不会因粗粒度锁拖慢其他线程。
迭代和遍历时注意弱一致性语义
ConcurrentHashMap 的迭代器不抛 ConcurrentModificationException,也不保证强一致性——它反映的是迭代开始时的快照,后续修改可能部分可见、也可能不可见。
- 不要假设遍历中能实时看到其他线程刚做的
put,尤其在统计类逻辑中需明确是否接受近似结果 - 若必须强一致(极少见),应改用外部同步或复制快照:
new HashMap(concurrentMap)再遍历 - 避免在遍历中调用
remove()或put()修改当前 map,虽不报错但行为不可控
性能优势来自锁粒度变化,别自己加锁
有人习惯给 ConcurrentHashMap 外层再套 synchronized,这是典型误区——等于主动退化成全局锁。
- 读操作(
get、containsKey)本身无锁,加 synchronized 反而制造瓶颈 - 写操作(
put、remove)默认只锁对应桶(链表头或红黑树根),多个线程操作不同 key 互不影响 - 如果业务确实需要跨 key 的原子组合(如转账:A 减、B 加),那该用更高层协调(如分布式锁、数据库事务),而不是锁整个 map











