上次已经为大家介绍过java中ConcurrentHashMap的概念是什么的有关内容了,相信大家对于ConcurrentHashMap已经有了一个初步的了解,关于它的存取实现也是一个比较重要的知识点,一起来看看吧。
主要包括下面这些内容:
1.put(): 对当前的table进行无条件自循环直到put成功
新版本1:
1. 如果没有初始化就先调用initTable()方法来进行初始化过程
2. 如果没有hash冲突就直接CAS插入
3. 如果还在进行扩容操作就先进行扩容(ForwardingNode的hash值判断)
4. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
5. 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环(树化)
6. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容。
然后是,版本2:
1判空;ConcurrentHashMap的key、value都不允许为null
2计算hash。利用方法计算hash值。
3遍历table,进行节点插入操作,过程如下:
如果table为空,则表示ConcurrentHashMap还没有初始化,则进行初始化操作:initTable()
根据hash值获取节点的位置i,若该位置为空,则直接插入,这个过程是不需要加锁的。计算f位置:i=(n - 1) & hash
如果检测到fh=f.hash==-1,则f是ForwardingNode节点,表示有其他线程正在进行扩容操作,则帮助线程一起进行扩容操作
如果f.hash>=0表示是链表结构,则遍历链表,如果存在当前key节点则替换value,否则插入到链表尾部。如果f是TreeBin类型节点,则按照红黑树的方法更新或者增加节点
若链表长度>TREEIFY_THRESHOLD(默认是8),则将链表转换为红黑树结构
4调用addCount方法,ConcurrentHashMap的size+1
如果一个或多个线程正在对ConcurrentHashMap进行扩容操作,当前线程也要进入扩容的操作中。这个扩容的操作之所以能被检测到,是因为transfer方法中在空结点上插入forward节点,如果检测到需要插入的位置被forward节点占有,就帮助进行扩容;
如果检测到要插入的节点是非空且不是forward节点,就对这个节点加锁,这样就保证了线程安全。尽管这个有一些影响效率,但是还是会比hashTable的synchronized要好得多。
接下来是,get()
1. 计算hash值,定位到该table索引位置,如果是首节点符合就返回
2. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回
3. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null
最后说一下,概括版:
(1)对于get读操作,如果当前节点有数据,还没迁移完成,此时不影响读,能够正常进行。
如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时get线程会帮助扩容。
(2)对于put/remove写操作,如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时写线程会帮助扩容,如果扩容没有完成,当前链表的头节点会被锁住,所以写线程会被阻塞,直到扩容完成。
其实java中关于ConcurrentHashMap的存取实现还是有一些复杂的,需要大家花费一定的时间去了解和掌握。如果大家想要了解更多的常见问题,敬请关注奇Q工具网哦。
推荐阅读: