ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

JDK 1.8 之前

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。

Segment 继承了 ReentrantLock ,所以 Segment 是⼀种可重⼊锁,扮演锁的⻆⾊。 HashEntry ⽤于
存储键值对数据。

static class Segment<K,V> extends ReentrantLock implements Serializable {}

⼀个 ConcurrentHashMap ⾥包含⼀个 Segment 数组, Segment 的个数⼀旦初始化就不能改变。
Segment 数组的⼤⼩默认是 16,也就是说默认可以同时⽀持 16 个线程并发写。Segment 的结构和 HashMap 类似,是⼀种数组和链表结构,⼀个 Segment 包含⼀个 HashEntry数组,每个 HashEntry 是⼀个链表结构的元素,每个 Segment 守护着⼀个 HashEntry 数组⾥的元素,当对 HashEntry 数组的数据进⾏修改时,必须⾸先获得对应的 Segment 的锁。也就是说,对同⼀ Segment 的并发写⼊会被阻塞,不同 Segment 的写⼊是可以并发执⾏的。

JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现有什么不同?

  • 线程安全实现⽅式:
    • JDK 1.7 采⽤ Segment 分段锁来保证安全, Segment 是继承⾃ReentrantLock 。
    • JDK1.8 放弃了 Segment 分段锁的设计,采⽤ Node + CAS + synchronized 保证线程安全,锁粒度更细, synchronized 只锁定当前链表或红⿊⼆叉树的⾸节点。
  • Hash 碰撞解决⽅法:
    • JDK 1.7 采⽤拉链法,JDK1.8 采⽤拉链法结合红⿊树(链表⻓度超过⼀定阈值时,将链表转换为红⿊树)
  • 并发度:
    • JDK 1.7 最⼤并发度是 Segment 的个数,默认是 16。JDK 1.8 最⼤并发度是 Node 数组的⼤⼩,并发度更⼤。

JDK 1.8 之后

ConcurrentHashMap 取消了 Segment 分段锁,采⽤ Node + CAS + synchronized 来保证并发安全。数据结构跟 HashMap 1.8 的结构类似,数组+链表/红⿊⼆叉树。Java 8 在链表⻓度超过⼀定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红⿊树(寻址时间复杂度为 O(log(N)))。