ConcurrentHashMap JDK8 为什么只锁住链表首节点?原理详细拆解
一、先对比 JDK7 缺点
JDK7 使用 Segment 分段锁整个哈希表划分十几个分段,锁住一整段数组,只要这段里面任意一个链表修改,整段都不能并发操作。锁的范围太大,竞争多、并发效率低。
二、JDK8 底层结构变化
JDK8 结构:数组 + 链表 + 红黑树
1. 哈希数组每一个下标位置,单独存放一条链表 / 红黑树
2. 不同下标位置的数据互相完全独立、互不干扰
三、为什么加锁只锁当前链表第一个节点?
1. 数据隔离本质
哈希桶【数组每一个索引位置】都是相互独立
- 桶 A 的数据,不会存到桶 B
- 修改当前这一个桶的数据,完全不会影响其他桶
那根本不需要锁住整个数组、不需要锁住整片区间。只需要锁住当前正在修改的这一条链表就行
2. 链表所有元素都依附头节点
一条链表结构:头节点→第二个→第三个→尾结点整条链表所有后续节点,全部依托首位头节点挂载。
只要把链表头节点上锁:
- 整条链表新增、删除、修改、替换节点全部被锁住
- 链表内部所有元素线程全部阻塞,不会并发篡改
等同于锁住了整条链表,但是锁的对象只有一个,范围极致缩小
3. 极大缩小锁粒度,减少锁竞争
1. 如果锁数组:所有线程全部争抢同一把锁,并发直接废掉
2. 如果锁分段:同一个分段下所有桶互相抢占锁
3.锁住单个桶首节点不同下标桶可以同时并发写入,互不阻塞。一百个线程可以同时操作一百个不同位置,并发能力拉满
4. 搭配 CAS 无锁优化,进一步降低加锁次数
JDK8 写入流程:
1. 数组位置为空,直接 CAS 无锁插入,全程不加锁
2. 数组位置已有链表节点,才会启用 synchronized
3. 并且仅仅锁住该桶头结点
只有同一个桶位置才会产生锁竞争,不同桶永远不会争抢锁
5.synchronized 本身优化(关键点)
jdk1.6 之后 synchronized 做了锁升级:偏向锁→轻量级锁→重量级锁,锁对象越小、锁对象数量越多,偏向锁、轻量级锁生效越好,开销极低。如果锁住大对象、大范围,锁升级开销会非常大。只锁单个节点,锁本身性能损耗降到最低。
四、完整总结理由
1. 哈希表每个桶数据相互独立,修改单个桶无需锁住其他位置
2. 一条链表全部节点都挂载在头节点,锁首节点 = 锁住整条链表
3. 锁粒度压缩到最小,大幅度减少多线程锁竞争
4. 多桶可以并行操作,整体并发吞吐量大幅提升
5. 配合 CAS 无锁机制,尽量少加锁;同时依托 synchronized 锁升级机制,锁性能更好
五、面试精简背诵版
1.JDK8 哈希桶彼此相互独立,互不影响,无需大范围加锁。
2. 整条链表全部节点依托头节点存储,锁定首节点即可锁住整条链表。
3. 缩小锁粒度,避免大范围锁竞争,多线程可以并行操作不同哈希桶。
4. 优先使用 CAS 无锁操作,只有哈希桶存在元素时才加锁,结合 synchronized 锁升级,并发效率远高于 JDK7 分段锁。
更多推荐




所有评论(0)