在Java里如何使用ConcurrentHashMap实现线程安全Map_Java并发集合解析

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):存在则合并,不存在则插入,原子

注意:mappingFunctionremappingFunction 内部不应有阻塞或长耗时逻辑,否则会拖慢整个桶的写入性能。

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 判断是否可消费,改用 !m

    ap.isEmpty()
    + map.remove(key) 组合,并捕获 null 返回值
  • 避免在循环里反复调用 size() 控制退出条件,容易漏数据或死循环

迭代器弱一致性:for-each 遍历时修改不会抛 ConcurrentModificationException

ConcurrentHashMapkeySet().iterator()entrySet().iterator() 是弱一致性的:它不抛 ConcurrentModificationException,也不保证反映“最新全部状态”。你可能看到部分新增元素,也可能看不到刚删除的元素,但绝不会抛错或崩溃。

这种设计是为了让遍历不阻塞写入线程,代价是语义上更像“某一时刻的快照切片”而非完整 snapshot。

ConcurrentHashMap map = 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) 拷贝 —— 注意这会短暂阻塞写入,且拷贝本身有内存和时间开销。