一、先对比 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 分段锁。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐