简析JUC中ConcurrentHashMap的实现原理

HashMap在并发环境下会出现死循环等问题,其不是并发安全的。使用并发安全的HashTable或者Collections.synchronizedMap(hashMap),其性能又很低,因为这两个方案对读写进行加锁,一个线程在读写元素,其它线程必须等待。

于是Doug Lea贡献出了并发安全又性能优良的ConcurrentHashMap,它通过减小锁粒度的方式来提高并发性能。所谓减小锁粒度,就是指缩小锁定对象的范围,从而降低锁冲突的可能性,进而提高并发能力。

ConcurrentHashMap在JDK1.8中有较大变化,所以我们分别来看看JDK1.7和JDK1.8中的ConcurrentHashMap的实现原理。

JDK1.7中ConcurrentHashMap的实现原理

在JDK1.7中ConcurrentHashMap底层采用数组+链表的存储结构,使用分段锁的机制来实现并发的更新操作。

实现核心功能的两个类是:Segment和HashEntry。

  • Segment继承自ReentrantLock用来充当锁的角色,每个Segment对象守护散列映射表的若干个桶。
  • HashEntry用来封装散列映射表的键值对。
  • 每个桶是由若干个HashEntry对象链接起来的链表。

一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组,下面我们通过一个图来演示一下 ConcurrentHashMap 的结构:

JDK1.8中ConcurrentHashMap的实现原理

JDK1.8中摒弃了Segment的概念,底层使用数组+链表+红黑树实现,用synchronized和CAS来实现并发控制,看起来就像是优化过且线程安全的HashMap。synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,所以效率提升很多。

JDK1.8中的实现也是锁分离的思想,它把锁分的比segment(JDK1.7)更细一些,只要hash不冲突,就不会出现并发获得锁的情况。它首先使用无锁操作CAS插入头结点,如果插入失败,说明已经有别的线程插入头结点了,再次循环进行操作。如果头结点已经存在,则通过synchronized获得头结点锁,进行后续的操作,性能比segment分段锁又再次提升。


每日学习笔记,写于2020-05-05 星期二

你可能感兴趣的:(简析JUC中ConcurrentHashMap的实现原理)