concurrenthashmap 通过分段锁(jdk 1.7)和 cas+桶级 synchronized(jdk 1.8+)实现高并发安全,保障单操作原子性但不保证复合操作线程安全,需避免超卖等业务逻辑问题。

ConcurrentHashMap 在多线程高并发场景中不是靠“一把大锁”来保证安全,而是通过分段锁(JDK 1.7 及以前)和 CAS + 桶级 synchronized(JDK 1.8+)这两套演进策略,把锁的范围缩到最小、把无锁操作用到最多。
分段锁:把大表切成小段,各锁各的
在 JDK 1.7 及之前,ConcurrentHashMap 内部维护一个 Segment 数组,默认 16 段。每个 Segment 是一个独立的 ReentrantLock + 小型 HashEntry 表:
- 线程 A 往段 0 插数据,线程 B 往段 5 插数据 → 完全不抢锁,真正并行
- 但若 A 和 B 都往段 3 写,就会竞争同一把锁,串行执行
- 读操作不加锁,靠 volatile 保证可见性,所以读性能极高
- 缺点也很明显:段数固定、跨段统计 size() 要遍历全部段、内存占用比普通 HashMap 高
CAS + 桶级锁:细到单个数组槽位的控制
JDK 1.8 彻底去掉 Segment,改用更轻量的机制:
- 数组每个桶(即 table[i])是独立的临界资源;只有写冲突时才对那个桶的头节点加 synchronized
- CAS 用在低竞争场景:比如桶为空时直接插入新节点、初始化 table、更新计数器 baseCount
- volatile 修饰 Node 的 val 和 next 字段,确保其他线程能立即看到修改
- 当链表长度 ≥ 8 且 table.length ≥ 64,自动转红黑树,避免长链导致查找退化
别误以为“用了就万事大吉”
ConcurrentHashMap 保障的是单个操作(get/put/remove)的原子性,不是复合操作:
- 错误写法:先 get 判断库存 > 0,再 put 扣减 → 中间可能被其他线程改掉,导致超卖
- 正确做法:用 computeIfPresent、replace 或显式加锁,或改用原子整数类(如 LongAdder)配合业务逻辑
- 扩容也不是“一个人干完”,而是多线程协助迁移——但频繁扩容仍会拖慢吞吐,建议预估容量、合理设置 initialCapacity 和 loadFactor
实际选型建议
如果你还在用 JDK 1.7 或需兼容老版本,理解 Segment 分段逻辑有助于排查锁竞争热点;但当前主流(JDK 8+)应聚焦于桶级锁行为和 CAS 触发条件:
- 监控 GC 和线程阻塞情况,若大量线程卡在 synchronized 块里,说明哈希分布不均或桶冲突严重
- 避免 key 的 hashCode 实现不合理(如总返回同一值),否则所有写都挤在一个桶,退化成单线程
- 读远多于写的场景,它几乎零开销;写密集且 key 分布差时,性能优势会打折扣
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











