问五:说说ConcurrentHashMap吧?

引入:如何优化Hashtable?

通过锁细粒度化,将整个锁拆解成多个锁进行优化

 

这儿就引入了早期的ConcurrentHashMap

使用的是分段锁技术,把Bucket分成几段来存储,为每一段数据都配一把锁(segment)【这样做的原因是:为每个Bucket都添加一把锁的话,资源消耗大,比如1w个Bucket就要有1w个锁

当某个程序访问某个数据段的时候,就会获得当前数据段(segment)的锁,操作其子数组。而如果还有程序要访问,就只能被阻塞,而其他未被访问到的数据段依旧可以访问

早期ConcurrentHashMap分配了16个segment,理论上较Hashtable会提高16倍的效率

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

Segment是一个可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。

一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素。当对HashEntry数组的数据进行修改时,必须首先获得与它对应的segment锁。

 

这儿就引入了现如今的ConcurrentHashMap,通过CAS和synchronized使锁进一步细化,结构和JDK1.8的HashMap相同

附:HashMap是在package java.util包下的,但是ConcurrentHashMap是在package java.util.concurrent下的

 

concurrent分段如何实现的?具体一点

 

 

源码分析:

里面有很多参数和HashMap相同

也有独特的参数,比如这个控制table大小的变量

进入到源码中,直接看put方法,发现里面一开始就有个对key、value判空的条件:

也就是说ConcurrentHashMap是不能够存放空值的,而HashMap则可以。

然后下一行,能够看到ConcurrentHashMap对hash值的确定:

由于对数组更新是使用CAS来进行更新的,所以需要不断去做失败重试,直到成功为止

如果发生了hash碰撞,ConcurrentHashMap的处理逻辑:

 

ConcurrentHashMap:put方法的逻辑

1.判断Node[]数组是否初始化,没有就进行初始化操作

2.通过hash定位数组的索引座标,是否有Node节点,如果没有就使用CAS进行添加(链表的头结点),添加失败则进入下一次循环

3.如果检查到内部正在扩容,就帮助它一块扩容

4.如果f!=null,则使用synchronized锁住f元素(f元素就是链表或者红黑二叉树的头元素)

    4.1 如果是Node,则执行链表的添加方法

    4.2 如果是TreeNode,则执行树添加结构

5.判断链表长度是否已经达到临界值88是默认值,可以调整),当节点数超过这个值,就需要把链表转化为树结构

 

ConcurrentHashMap的总结:比起Segment,锁拆的更细

1.首先使用无锁操作CAS插入头结点,失败则循环重试

2.若头结点已经存在,则尝试获取头结点的同步锁,再进行操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章