ConcurrentHashMap实现线程安全的方式


衍生问题:

  • ConcurrentHashMap 和 HashTable 的区别;
  • ConcurrentHashMap 和 HashMap 的区别;

本文介绍 ConcurrentHashMap 是怎么实现线程安全的,主要根据其在不同 JDK 版本下的实现。

源码解析:还不懂 ConcurrentHashMap ?这份源码分析了解一下 (qq.com)

ConcurrentHashMap 的底层数据结构以及线程安全实现

在 JDK 1.7 时,采用分段 Segment 数组+链表实现。JDK 1.8 以后采用和 HashMap 1.8 时的一样,都是数组 + 链表/红黑树。但是这个数组的节点是一个一个的 Node 节点。

JDK 1.8 之前:

实现原理:指对数据进行分段,对每一部分数据分别加一个分段锁 Segment,每一个段对应一个 HashEntry 数组,这个数组采用 key-value 结构存储键值对数据,值的类型为链表。当需要对某个 HashEntry 数组进行修改时,会先获取对应的 Segment 锁。

多线程对于同一段的操作会造成堵塞,不同段可以并发。

Segment :继承了 ReentrantLock 锁,所以是一种可重入锁,由于实现方式的底层是一个 Segment 数组,所以段的个数是不可改变的(默认大小 16)。

可重入锁不清楚了可以参考:JUC–永久笔记–AQS队列同步器与ReentrantLock可重入锁

JDK 1.8 之后

通过 Node 数组 + CAS + synchronized 实现并发安全;结构为数组 + 链表/红黑树

与 JDK 1.8 之前的实现方式对比:

  1. 数据结构不同
  2. 锁的粒度更细,改用了 Node 作为锁的对象
  3. 优化了出现 Hash 冲突时的数据存储,和 HashMap 1.8 之后类似,数组和链表超过一定长度会转换为红黑树。
  4. 由于是使用 Node 数组,节点个数更多,所以并发度更高。

和 HashTable 的区别

底层数据结构:

  1. HashTable 底层是由桶数组 + 链表实现
  2. ConcurrentHashMap 1.8 之前是 Segment 数组+链表。1.8 之后是 Node 数组+链表/红黑树。

实现线程安全的方式不同:

  1. HashTable 是通过 Synchronized 进行加锁,当进行锁的时候会锁住整个桶数组,其他线程不能进行元素的修改和读取,竞争很激烈,因此效率较低。
  2. ConcurrentHashMap 1.7 的时候,当修改某个 entry 数组下的元素时,会把 entry 所在的 Segment 段加锁,其他线程如果也想访问到同一个 Segment 下的元素,就必须等待锁的释放。此时其实已经比 HashTable 的效率高很多了,但是仍然会发生线程竞争问题。因此 1.8 之后采用了 Node 数组+链表/红黑树实现,和 HashMap 的结构类似,一个 Node 数组对应一个链表或者红黑树,每次只需要锁住某个 Node 节点即可实现线程安全问题,这时锁的粒度更细,也就减少了线程竞争访问问题。

容量问题:

  1. HashTable 在初始化之后,Segment 数组就不能进行扩容了,是固定的,不够灵活。
  2. Node 数组由于是链表节点,因此可以无限扩容。

确实是链表节点。


文章作者: KTpro
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 KTpro !
评论
  目录