欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【面试题】ConcurrentHashMap实现线程安全的底层原理到底是什么?

程序员文章站 2022-04-19 15:36:56
...

JDK1.7以及之前的版本,多个数组,分段加锁,一个数组一个锁

JDK1.8及以后的版本,优化细粒度,整合为一个数组,对数组中每个元素进行CAS,如果CAS失败了说明当前有人了,此时synchronized对数组元素加锁,使用链表+红黑树进行处理,对数组每个元素加锁。

目前较多情况下,多线程要同时读写一个HashMap

原始用法

HashMap map = new HashMap();
synchronized(map){
    map.put("xxxx","yyyy");
}

如果线程1put数组[5],线程2put数组[21],其实这两个操作互不影响,除非同时对一个元素执行put操作,才需要多线程进行同步。

 

改用ConcurrentHashMap(JDK并发包中默认实现了线程安全性的ConcurrentHashMap)

ConcurrentHashMap map = new ConcurrentHashMap();
map.put("xxxx","yyyy");

在JDK1.7以及之前的版本里,分段[数组1],[数组2],[数组3],每个数组对应一个锁,分段加锁

JDK1.8之后进行了优化和改进,利用CAS策略,可以保证并发安全性。(只有对同一个元素进行操作才会用,而多线程对数组中不同的元素执行put时,其实互不影响)

同一时间,只有一个线程能成功执行这个CAS,就是说刚开始先获取一下数组[5]这个位置的值,如果为null,然后执行CAS,compareAndSet将值设置进去,同时其他的线程执行CAS都会失败

通过对数组每个元素执行CAS的策略,如果是很多线程对数组里不同的元素执行put,互不影响。如果其他线程失败了,表示数组[5]这个位置有线程完成了put操作。这个时候需要再这个位置基于链表+红黑树进行处理,synchronized(数组[5])加锁,基于链表或是红黑树在这个位置将自己的数据put进去

如果你是对数组里同一个位置的元素进行操作,才会加锁串行化处理;如果是对数组不同位置的元素进行操作,此时大家可以并发执行

 

【评论区】

看一下put操作的流程(初始化,上锁,扩容,树化,synchronized)过程

从源码上看,PUT操作只有在NULL时才会CAS

1.ConcurrentHashMap进行put操作的时候,元素为null,则进行cas,不为null,则进行synchronized同步,不为null的时候,为什么也不进行cas呢?(这块的解答看不懂)

因为此时数组得位置放的是链表或者红黑树得引用.而不是具体得值

当数组得位置存放得是链表或者红黑树得引用得时候,此时已经发生了一次哈希冲突了,讲道理,哈希冲突得概率有,但不是很高,链表里面得数据进行添加得时候,运用cas操作是比较麻烦得,因为在多线程情况下往链表里面插数据是会报错得,这个时候只能采用sync锁来进行了.cas是比较后set,那对链表来讲,这个操作是实现不了的,链表在内存中不连续,节点的增加记录下上一个的位置就行,cas操作跟谁比较,怎么比较,hashmap的entry是个单向链表,cas操作中链表的指针如果不为null,那就得到另一个地址在进行比较,在这个过程中,链表长度突破8,对于后续的转化红黑树的操作影响也是非常巨大的.总的来讲,就是这个过程中进行cas的消耗,以及对编码的复杂度,都没有sync来的方便快捷,哈希冲突也是个小概率事件,对数组的不同位置加锁或者cas操作,已经完全够用了

 

元素不为null,当然不能走CAS,因为数组的元素实际上是一个链表或者红黑树的节点的引用,你要怎么对该节点直接CAS呢?只能先用synchronized(数组元素)锁住后,再对链表或者红黑树做增删操作

 

相关标签: 线程