concurrenthashmap 在100线程、7:3读写比下吞吐量是 synchronizedmap 的3–8倍,因其读操作无锁、写操作仅锁对应哈希桶,而后者所有方法共用同一把锁;且提供原子复合操作,而 synchronizedmap 仅保证单方法原子性。

ConcurrentHashMap 比 SynchronizedMap 快多少?
在 100 线程、中等读写比(7:3)的典型场景下,ConcurrentHashMap 的吞吐量通常是 Collections.synchronizedMap(new HashMap()) 的 3–8 倍,具体取决于操作模式。根本原因不是“锁粒度更细”这种模糊说法,而是 ConcurrentHashMap 在 JDK 8+ 彻底去掉了全局锁:读操作完全无锁,写操作只锁对应 Node 所在的哈希桶(bin),而 SynchronizedMap 所有方法都走同一个 mutex 对象,一写全堵。
- 纯读场景:两者性能几乎持平,但
ConcurrentHashMap内存开销略高(多出一些并发控制字段) - 高写低读(如实时计数器):
ConcurrentHashMap优势拉满;SynchronizedMap容易成为 CPU 瓶颈,Thread.getState()经常看到一堆WAITING线程卡在monitor enter - 注意 JDK 版本差异:JDK 7 的
ConcurrentHashMap用分段锁(Segment),JDK 8+ 改用synchronized+CAS控制头节点,后者在低竞争时更快,高竞争时更稳
为什么不能直接把 SynchronizedMap 当作线程安全兜底方案?
它只保证单个方法调用的原子性,不保证复合操作的线程安全——这是最常踩的坑。比如 map.containsKey(key) && map.get(key) != null 看似简单,但在 SynchronizedMap 下依然可能返回 null 或抛 NullPointerException,因为两次方法调用之间 map 可能已被其他线程修改。
- 典型翻车现场:
map.putIfAbsent(key, value)这种原子操作,SynchronizedMap根本不提供,你得自己加锁包裹,反而破坏了它的“自动同步”假象 -
ConcurrentHashMap明确提供computeIfAbsent、merge、replace等原子复合方法,语义清晰,无需手写锁 - 迭代器行为不同:
SynchronizedMap的iterator()是“弱一致性”,但实际仍可能抛ConcurrentModificationException(取决于外部是否在遍历时修改);ConcurrentHashMap的迭代器是“弱一致性且不抛 CME”,但可能看不到最近写入
ConcurrentHashMap 的 size() 和 isEmpty() 为什么不准?
这两个方法返回的是近似值,不是强一致结果。因为要避免为统计而阻塞所有写操作,ConcurrentHashMap 采用异步更新计数器的方式:每次写操作通过 addCount 更新一个 volatile long 类型的 baseCount,并在扩容或竞争激烈时使用 CounterCell 数组分片累加。最终 size() 是 baseCount 加上所有 CounterCell 的和,但这个过程本身不加锁,所以可能漏掉正在执行中的写操作。
- 如果你需要精确 size(比如做容量校验),要么加外部读锁(失去并发意义),要么改用
LongAdder单独维护计数 -
isEmpty()同理:它只检查baseCount == 0,但此时可能已有线程刚写入、计数器还没刷到 baseCount —— 所以它返回true时一定为空,返回false时不保证非空 - 别在 if 判断里依赖
size() > 0来决定是否 get,应该直接get(key)并判 null
什么时候还该考虑 SynchronizedMap?
极少,但存在:当你明确知道并发写极少(比如配置加载后只读)、且整个 map 生命周期内不会出现复合逻辑、同时又想最小化内存占用和 GC 压力时,SynchronizedMap 的对象结构更轻量(没有 CounterCell 数组、没有树化逻辑、没有扩容状态位)。
立即学习“Java免费学习笔记(深入)”;
- 典型适用场景:Spring 的
SimpleThreadScope内部缓存、小型 CLI 工具的状态映射表、测试用的 mock map - 千万别因为“代码看起来短”就选它——
Collections.synchronizedMap(new HashMap())比new ConcurrentHashMap()多一次包装对象创建,但运行时开销差距早已被 JIT 抹平 - 如果只是怕
ConcurrentHashMap的复杂性,不如直接用java.util.concurrent.ConcurrentHashMap.newKeySet()或ConcurrentHashMap.ofEntries()(JDK 9+)简化初始化
真正难的不是选哪个类,而是想清楚“我到底需不需要强一致性语义”——很多业务场景里,近似 size、弱一致迭代、延迟可见的写入,反而比死等一个精确值更健壮。











