ConcurrentHashMap --- putVal
2020-03-27 16:05:38来源:博客园 阅读 ()
首先贴 java 8 中实现的源代码
1 /** Implementation for put and putIfAbsent */ 2 final V putVal(K key, V value, boolean onlyIfAbsent) { 3 if (key == null || value == null) throw new NullPointerException(); 4 int hash = spread(key.hashCode()); 5 int binCount = 0; 6 for (Node<K,V>[] tab = table;;) { 7 Node<K,V> f; int n, i, fh; 8 if (tab == null || (n = tab.length) == 0) // 第一次使用时,整个 table 为 null 9 tab = initTable(); 10 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 11 if (casTabAt(tab, i, null, 12 new Node<K,V>(hash, key, value, null))) 13 break; // no lock when adding to empty bin 14 } 15 else if ((fh = f.hash) == MOVED) // 当节点的 hash 值为 -1 时,代表这个节点为 ForwardingNode, 其后续节点需要转移 16 tab = helpTransfer(tab, f); 17 else { 18 V oldVal = null; 19 synchronized (f) { 20 if (tabAt(tab, i) == f) { // 再次获得热区代码执行权后,首先确保之前记录的第 i 个节点没有变化(即没被迁移走) 21 if (fh >= 0) { // 确保当前节点的 hash 值代表正常的节点(其他的辅助型节点,比如 ForwardingNode,TreeBin,ReservationNode) 22 binCount = 1; 23 for (Node<K,V> e = f;; ++binCount) { 24 K ek; 25 if (e.hash == hash && 26 ((ek = e.key) == key || 27 (ek != null && key.equals(ek)))) { // 判断目标节点的根据是 spread 后的hash值相同并且(key 的 reference 相同或者 key 相等) 28 oldVal = e.val; 29 if (!onlyIfAbsent) 30 e.val = value; 31 break; 32 } 33 Node<K,V> pred = e; 34 if ((e = e.next) == null) { // 如果到达当前链表的尾节点,则 append 新的元素 35 pred.next = new Node<K,V>(hash, key, 36 value, null); 37 break; 38 } 39 } 40 } 41 else if (f instanceof TreeBin) { // 当 fh 小于 0,且为 -2 时,此时元素类型为 TreeBin 42 Node<K,V> p; 43 binCount = 2; 44 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 45 value)) != null) { 46 oldVal = p.val; 47 if (!onlyIfAbsent) 48 p.val = value; 49 } 50 } 51 } 52 } 53 if (binCount != 0) { 54 if (binCount >= TREEIFY_THRESHOLD) // 当插入元素后,单个 slot 的链表的节点数目大于等于 8 时,进行红黑树化这个 slot 55 treeifyBin(tab, i); 56 if (oldVal != null) 57 return oldVal; 58 break; 59 } 60 } 61 } 62 addCount(1L, binCount); 63 return null; 64 }
需要注意的几个关键点:
1. 如何从 key 定位其所在的 slot
我们常采用 (CAP-1) & spread(key.hashCode()) 来确定我们要存储的 key 在 HashMap 底层存储的 index或者 slot (参见上述代码中的第 10 行)。获取某个key所对应的元素如下:
f = tabAt(tab, i=(n-1)&hash)
注意这里的 n 为当前 table 的大小。在 ConcurrentHashMap 的实现中, n 的默认大小为 16 (1<<<4) ,当发现当前 table 实际存储的大小大于等于n 等于 n - (n>>>2) ,即默认的 loadFactor 0.75 时,原来数组进行扩容,
变为原来的 2 倍,即 32, 之后依次累增,64,128,256,512,1024 ... 直到 1 <<< 30
2. table 的初始化
这块部分我们已经在另一篇随笔里面讲述了,参见 ConcurrentHashMap --- 怎样在高并发环境下初始化一个数组
3. slot 为空时添加元素
获取或者更新 table 中的元素时,我们采用 CAS 无锁的形式操作,具体方法为 tabAt,casTabAt,参见第 10, 11 行。至于为何要使用这种方式,参见 ConcurrentHashMap --- 第一 cas 操作数组元素
4. slot 不为空时添加元素
当 slot 不为空时,判断当前的 hash 值是否大于0 (ConcurrentHashMap为了在多线程环境下 resize引入了一些特殊的节点类型(ForwardingNode,TreeBin,ReservationNode),其hash值可能为负数)。
当 hash 值大于 0,即为正常的有意义的节点时,遍历以该节点为头的链表,如果找到对应的 key,则更新新值,否则append 新建的节点到链表的末尾。
遍历过程中判断 key 相等的方法如下:
e.hash && ((ek = e.key) == key || key.equals(ek))
在这里额外插入一点。
假如,我们要存储的两个 key (k1, k2) 其实是相等的元素,但是我们只实现了 hashcode,而没有实现 equals 方法,则实际存储时,k1和 k2 会存放在一个 slot 的链表下面,
但是因为不相等,会存储为两个不同的元素
同理,假如两个 key (k1, k2) 其实是相等的元素,但是我们只实现了 equals,而没有实现 hashcode 方法,则实际存储时,k1和 k2 会存放在两个不同的 slot 下面
所为,如果认定两个 key 值可能引用不同,但值相等,我们既要重写其 equals 方法,又要重写其 hashCode 方法
5. 特殊元素的插入
遍历中如果发现元素为 TreeBin 类型,此时采用红黑树的插入实现
6. 树化
第 4 步插入元素后,可能链表过长,此时会将链表转换为树结构,见第 54 行
7. 增加 sizeCtl
sizeCtl 控制当前 table 可以添加的最大元素。每次添加元素后,统计当前 table 实际存储的元素个数,如果大于等于 sizeCtl,则进行扩容 resize
原文链接:https://www.cnblogs.com/neocxf/p/12581015.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 设计模式---类之间的关系知多少 2020-06-07
- java方法句柄-----1.方法句柄类型、调用 2020-05-28
- Java--Java的设计模式----单例模式 2020-05-26
- SpringBoot+Mybatis---这一篇就够了! 2020-05-18
- JDBC学习一---JDBC入门 2020-05-03
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash