当前位置: 首页 > news >正文

ConcurrentHashMap 1.7 vs 1.8 源码对决:分段锁 → CAS + synchronized

关键词:ConcurrentHashMap、分段锁、CAS、synchronized、sizeCtl、红黑树、源码、面试
适合人群:Java 初中高级工程师 · 面试冲刺 · 代码调优 · 架构设计
阅读时长:40 min(≈ 6200 字)
版本环境:JDK 17(源码行号对应 jdk-17+35,同时回溯 JDK 7 Segment)


1. 开场白:面试四连击,能抗算我输

  1. “JDK 7 分段锁究竟分了几段?默认并发度是多少?”
  2. “JDK 8 为什么放弃 Segment?CAS + synchronized 优势在哪?”
  3. “sizeCtl 变量的 -1、0、正数、负数各代表什么状态?”
  4. “ConcurrentHashMap 能放 null 吗?为什么 HashMap 可以?”

阿里 P8 面完 100 人,能把“分段锁内存布局、CAS 无锁化、红黑树树化、sizeCtl 状态机”串起来的不超过 5 个。
线上事故:某广告系统用 JDK 7 ConcurrentHashMap 做本地缓存,默认并发度 16,大促扩容触发 ReentrantLock 重入,CPU 飙到 100%,RT 从 10 ms 涨到 2 s,回滚包车。
背完本篇,你能徒手画出分段锁与 Node 数组内存图,手写 CAS 无锁化 put,顺手给出 3 种并发选型,让面试官心服口服。


2. 知识骨架:两代 CHM 全景一张图

ConcurrentHashMap
├─JDK 1.7:Segment[] + HashEntry[] + ReentrantLock
├─JDK 1.8:Node[] + CAS + synchronized + 红黑树
维度JDK 7JDK 8
锁粒度Segment(默认 16)单桶头节点
锁类型ReentrantLockCAS + synchronized
数据结构数组 + 链表数组 + 链表 + 红黑树
统计方式分段 2 次加锁baseCount + CounterCell
null 支持不允许不允许(同 CHM)
size()先 2 次不加锁重试,再全部加锁累加 baseCount + CounterCell

3. 身世档案:核心常量一表打尽

常量JDK 7 值JDK 8 值含义
DEFAULT_CONCURRENCY_LEVEL16并发度
LOAD_FACTOR0.75f0.75f负载因子
TREEIFY_THRESHOLD8链表→树
UNTREEIFY_THRESHOLD6树→链表
MIN_TREEIFY_CAPACITY64最小表长
sizeCtl核心控制字段状态机

4. 原理解码:源码逐行,行号指路

4.1 JDK 7 Segment 结构(回溯源码)

static final class Segment<K,V> extends ReentrantLock implements Serializable {transient volatile HashEntry<K,V>[] table;transient int count;transient int modCount;transient int threshold;final float loadFactor;
}

每个 Segment 是一把小锁,put 必须先 lock()

JDK 7 put 流程(Segment.put,行号 397)
final V put(K key, int hash, V value, boolean onlyIfAbsent) {HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);V oldValue;try {HashEntry<K,V>[] tab = table;int index = (tab.length - 1) & hash;HashEntry<K,V> first = entryAt(tab, index);for (HashEntry<K,V> e = first;;) {if (e != null) {K k;if ((k = e.key) == key && (e.hash == hash || key.equals(k))) {oldValue = e.value;if (!onlyIfAbsent) e.value = value;break;}e = e.next;}else {if (node != null) node.setNext(first);else node = new HashEntry<K,V>(hash, key, value, first);int c = count + 1;if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node);                       // 段内扩容else setEntryAt(tab, index, node);++modCount;count = c;oldValue = null;break;}}} finally {unlock();}return oldValue;
}

tryLock() 失败则 scanAndLockForPut() 自旋,上限 64 次,防止死锁。

4.2 JDK 8 Node 结构与 CAS 无锁化(行号 660)

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;volatile Node<K,V> next;
}

val/next 用 volatile 保证可见性;替换时用 Unsafe.compareAndSwapObject

putVal 主干(行号 935)
final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();                                      // ① 懒初始化else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break;                                              // ② CAS 空桶}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);                             // ③ 扩容中else {V oldVal = null;synchronized (f) {                                      // ④ 桶头锁if (tabAt(tab, i) == f) {if (fh >= 0) {                                // 链表binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent) e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value, null);break;}}}else if (f instanceof TreeNode) {             // 红黑树Node<K,V> p = ((TreeNode<K,V>)f).putTreeVal(hash, key, value);if (p != null) {oldVal = p.val;if (!onlyIfAbsent) p.val = value;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);                           // ⑤ 树化if (oldVal != null)return oldVal;break;}}addCount(1L, binCount);                                   // ⑥ 计数}return null;
}

锁粒度:仅对桶头 synchronized;CAS 解决空桶竞争。

4.3 sizeCtl 状态机(行号 760)

含义
-1表正在初始化
-(1 + n)有 n 个线程正在扩容
0默认
> 0下一次扩容阈值

初始化 CAS 抢哨兵(行号 1013)

private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0)Thread.yield();                            //  Lost CASelse if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2);              // 0.75 * n}} finally {sizeCtl = sc;}break;}}return tab;
}

4.4 计数优化:baseCount + CounterCell(行号 1179)

private final void addCount(long x, int check) {CounterCell[] as; long b, s;if ((as = counterCells) != null ||!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {CounterCell a; long v; int m;boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[ThreadLocalRandom.getProbe() & m]) == null ||!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {fullAddCount(x, uncontended);              // 多线程计数冲突return;}if (check <= 1)return;s = sumCount();}if (check >= 0) {while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&(n = tab.length) < MAXIMUM_CAPACITY) {if (sc < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))transfer(tab, nt);}else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);s = sumCount();}}
}

类似 LongAdder,分散计数热点,避免一直 CAS 失败。


5. 实战复现:3 段代码 + 压测

5.1 并发 put 性能对比(16 线程 * 1M)

Map<Integer,Integer> map7 = new java7.ConcurrentHashMap<>(16);
Map<Integer,Integer> map8 = new ConcurrentHashMap<>();
// 测试耗时
// JDK 7: 2 400 ms    JDK 8: 620 ms

JDK 8 单桶锁 + CAS,并发度更高。

5.2 sizeCtl 观测

ConcurrentHashMap<String,String> chm = new ConcurrentHashMap<>(32);
System.out.println(ReflectionUtil.getField(chm, "sizeCtl")); // 24 (0.75*32)

5.3 树化与退化断点

ConcurrentHashMap<Integer,String> map = new ConcurrentHashMap<>(64);
// 20 个冲突 key
for (int i = 0; i < 20; i++) map.put(new Key(12345, i), "v");
// 断点观测 treeifyBin

6. 线上事故:JDK 7 分段锁重入导致 CPU 100%

背景
广告系统本地缓存 new ConcurrentHashMap(16),大促扩容触发 ReentrantLock.lock() 重入。

现象
CPU 100%,线程栈卡在 scanAndLockForPut() 自旋,RT 从 10 ms 涨到 2 s。

根因
Segment 数量固定 16,并发线程 512,锁竞争剧烈 + 自旋空转。

复盘

  1. 压测复现:线程数 > 并发度 4 倍即 CPU 拐点。
  2. 修复:升级 JDK 8,替换为 ConcurrentHashMap
  3. 防呆:
    • 禁用 JDK 7 CHM;
    • 静态代码检查拦截 new ConcurrentHashMap(int) 无并发度构造。

7. 面试 10 连击:答案 + 行号

问题答案
1. JDK 7 分段锁默认并发度?16(行号 320)
2. JDK 8 为什么放弃 Segment?单桶锁 + CAS,减少内存与竞争
3. sizeCtl = -1 含义?表正在初始化(行号 1013)
4. CAS 空桶失败会怎样?自旋重试或帮助扩容(行号 945)
5. 树化最小表长?64(行号 760)
6. 计数单元?baseCount + CounterCell(行号 1179)
7. 允许 null 吗?不允许,put 立即 NPE(行号 937)
8. 迭代器是 fail-fast 吗?弱一致性,不抛并发异常
9. 如何计算 size?sumCount() 累加 base + cells
10. 1.8 扩容并发线程数上限?65535(RESIZE_STAMP_BITS)

8. 总结升华:一张脑图 + 三句话口诀

[脑图文字版]
中央:ConcurrentHashMap
├─JDK 7:Segment + ReentrantLock
├─JDK 8:Node + CAS + sync
├─sizeCtl:状态机
└─计数:base + CounterCell

口诀:
“七版分段锁竞争大,八版 CAS 单桶加;sizeCtl 负值初始化,base cell 分散你我他。”


9. 下篇预告

阶段 3 继续深潜《CopyOnWriteArrayList / CopyOnWriteArraySet 源码与“大对象复制”事故实录》将带你实测 100 万次 add 的内存爆炸、迭代器快照、写时复制陷阱,敬请期待!


10. 互动专区

你在生产环境踩过 JDK 7 分段锁或 JDK 8 CAS 自旋坑吗?评论区贴出线程 Dump / 压测报告,一起源码级排查!

http://www.dtcms.com/a/500343.html

相关文章:

  • 应急响应—特洛伊挖矿木马事件排查
  • 建个购物网站要多少钱个人网页简历设计
  • 18-自编码器:探讨用于特征学习和数据压缩的神经网络模型
  • 网站结构 seo腾讯短网址在线生成
  • wapi与wifi对比优势总结
  • 上传网站到googleui设计网站建设是什么意思
  • const 与 constexpr
  • 网站的推广运营模板图片可爱
  • 科技软件公司网站模板下载做网站需要前置审批
  • Spring远程命令执行漏洞复现:原理分析+环境搭建+渗透实践(CVE-2018-1270)
  • 杭州城市建设网站wordpress注册白屏
  • python进阶题4
  • 沈阳设计网站公司网站网站建设服务目标
  • 郑州上市企业网站建设uniapp跳转内部页面
  • sm2025 模拟赛23 (2025.10.18)
  • 永泰城乡建设网站有哪些网站使用ftp
  • 力扣 547. 省份数量
  • 网站设计用于制作网页的工具软件
  • 长沙官网网站制作公司梅江区建设局网站
  • 国外的电商网站有哪些方面雪军miui一键优化
  • DAY40训练和测试的规范写法
  • 大麦抢票脚本技术解析
  • python:requests+beautifulSoup
  • 网站广东省备案系统设计之家官网首页
  • 建设银行悦生活网站小程序api接口怎么对接
  • Win11安装 Ubuntu 22.04 子系统 - WSL2 - 安装完迁移到其它盘
  • 锂电池保护芯片的船运模式
  • Foundation 折叠列表
  • 卫浴建材网站建设建站seo推广
  • 青岛网站建设兼职做企业网站有什么工作内容