ConcurrentHashMap 更适合高并发因其分段锁(JDK 8+为CAS+synchronized单Node),读无锁、写仅锁冲突桶;而Hashtable和synchronizedMap全局锁,吞吐量低5–10倍。

ConcurrentHashMap 为什么比 Hashtable 和 synchronized Map 更适合高并发
因为 ConcurrentHashMap 不是锁整个 map,而是分段加锁(JDK 8+ 改为 CAS + synchronized 锁单个 Node),读操作完全无锁,写操作只锁冲突的桶。而 Hashtable 所有方法都用 synchronized 方法锁住整个对象,Collections.synchronizedMap() 也一样——只要有一个线程在写,其他所有读写线程全得排队。
实际压测中,当并发写入量大、key 分布较散时,ConcurrentHashMap 吞吐量通常是 synchronizedMap 的 5–10 倍以上。
put()、computeIfAbsent()、merge() 这几个方法的线程安全边界在哪
它们各自保证「单次调用」的原子性,但不保证复合操作的线程安全。比如 map.get(key) == null && map.put(key, value) 仍是竞态条件,必须用 computeIfAbsent() 替代。
-
put(key, value):插入或替换,原子 -
computeIfAbsent(key, mappingFunction):仅当 key 不存在时才执行函数并插入,整个过程原子 —— 适合缓存初始化场景 -
merge(key, value, remappingFunction):存在则合并,不存在则插入,原子
注意:mappingFunction 和 remappingFunction 内部不应有阻塞或长耗时逻辑,否则会拖慢整个桶的写入性能。
立即学习“Java免费学习笔记(深入)”;
ConcurrentHashMap> cache = new ConcurrentHashMap<>(); cache.computeIfAbsent("user:1001", k -> { // 这里只会被一个线程执行,结果自动 put 进 map return loadRolesFromDB(k); // 但别在这里 sleep() 或发 HTTP 请求 });
size() 和 isEmpty() 在高并发下为什么不准
size() 返回的是估算值(JDK 8+),它通过累加每个 segment / bin 的计数,但过程中可能有其他线程正在增删,所以不是强一致。同理 isEmpty() 只检查 baseCount 是否为 0,也可能刚清空完就有人 put 了。
如果你需要精确统计,请改用 mappingCount()(返回 long,仍为估算,但比 size() 更准);如果真要 100% 精确,只能自己加锁遍历 —— 但这违背了使用 ConcurrentHashMap 的初衷。
- 不要用
size() == 0判断是否可消费,改用!map.isEmpty()+map.remove(key)组合,并捕获null返回值 - 避免在循环里反复调用
size()控制退出条件,容易漏数据或死循环
迭代器弱一致性:for-each 遍历时修改不会抛 ConcurrentModificationException
ConcurrentHashMap 的 keySet().iterator()、entrySet().iterator() 是弱一致性的:它不抛 ConcurrentModificationException,也不保证反映“最新全部状态”。你可能看到部分新增元素,也可能看不到刚删除的元素,但绝不会抛错或崩溃。
这种设计是为了让遍历不阻塞写入线程,代价是语义上更像“某一时刻的快照切片”而非完整 snapshot。
ConcurrentHashMapmap = new ConcurrentHashMap<>(); map.put("a", 1); new Thread(() -> map.put("b", 2)).start(); // 下面 for-each 可能输出 {a}、{a, b} 或只有 {b}(极小概率),但不会异常 for (String key : map.keySet()) { System.out.println(key); }
真正需要强一致性快照的场景(如导出全量状态做审计),得手动 new HashMap(map) 拷贝 —— 注意这会短暂阻塞写入,且拷贝本身有内存和时间开销。










