Java并发容器 (2)

正是由于这些规则和要求的限制,红黑树保证了较高的查找效率,所以现在就可以理解为什么 Java 8 的 ConcurrentHashMap 要引入红黑树了。好处就是避免在极端的情况下冲突链表变得很长,在查询的时候,效率会非常慢。而红黑树具有自平衡的特点,所以,即便是极端情况下,也可以保证查询效率在 O(log(n))。

事实上,链表长度超过 8 就转为红黑树的设计,更多的是为了防止用户自己实现了不好的哈希算法时导致链表过长,从而导致查询效率低,而此时转为红黑树更多的是一种保底策略,用来保证极端情况下查询的效率。

通常如果 hash 算法正常的话,那么链表的长度也不会很长,那么红黑树也不会带来明显的查询时间上的优势,反而会增加空间负担。所以通常情况下,并没有必要转为红黑树,所以就选择了概率非常小,小于千万分之一概率,也就是长度为 8 的概率,把长度 8 作为转化的默认阈值。

所以如果平时开发中发现 HashMap 或是 ConcurrentHashMap 内部出现了红黑树的结构,这个时候往往就说明我们的哈希算法出了问题,需要留意是不是我们实现了效果不好的 hashCode 方法,并对此进行改进,以便减少冲突。

源码分析

putVal方法,关键词:CAS、helpTransfer、synchronized、addCount

1 final V putVal(K key, V value, boolean onlyIfAbsent) { 2     if (key == null || value == null) { 3         throw new NullPointerException(); 4     } 5     //计算 hash 值 6     int hash = spread(key.hashCode()); 7     int binCount = 0; 8     for (Node<K, V>[] tab = table; ; ) { 9         Node<K, V> f; 10         int n, i, fh; 11         //如果数组是空的,就进行初始化 12         if (tab == null || (n = tab.length) == 0) { 13             tab = initTable(); 14         } 15         // 找该 hash 值对应的数组下标 16         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 17             //如果该位置是空的,就用 CAS 的方式放入新值 18             if (casTabAt(tab, i, null, 19                     new Node<K, V>(hash, key, value, null))) { 20                 break; 21             } 22         } 23         //hash值等于 MOVED 代表在扩容 24         else if ((fh = f.hash) == MOVED) { 25             tab = helpTransfer(tab, f); 26         } 27         //槽点上是有值的情况 28         else { 29             V oldVal = null; 30             //用 synchronized 锁住当前槽点,保证并发安全 31             synchronized (f) { 32                 if (tabAt(tab, i) == f) { 33                     //如果是链表的形式 34                     if (fh >= 0) { 35                         binCount = 1; 36                         //遍历链表 37                         for (Node<K, V> e = f; ; ++binCount) { 38                             K ek; 39                             //如果发现该 key 已存在,就判断是否需要进行覆盖,然后返回 40                             if (e.hash == hash && 41                                     ((ek = e.key) == key || 42                                             (ek != null && key.equals(ek)))) { 43                                 oldVal = e.val; 44                                 if (!onlyIfAbsent) { 45                                     e.val = value; 46                                 } 47                                 break; 48                             } 49                             Node<K, V> pred = e; 50                             //到了链表的尾部也没有发现该 key,说明之前不存在,就把新值添加到链表的最后 51                             if ((e = e.next) == null) { 52                                 pred.next = new Node<K, V>(hash, key, 53                                         value, null); 54                                 break; 55                             } 56                         } 57                     } 58                     //如果是红黑树的形式 59                     else if (f instanceof TreeBin) { 60                         Node<K, V> p; 61                         binCount = 2; 62                         //调用 putTreeVal 方法往红黑树里增加数据 63                         if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key, 64                                 value)) != null) { 65                             oldVal = p.val; 66                             if (!onlyIfAbsent) { 67                                 p.val = value; 68                             } 69                         } 70                     } 71                 } 72             } 73             if (binCount != 0) { 74                 //检查是否满足条件并把链表转换为红黑树的形式,默认的 TREEIFY_THRESHOLD 阈值是 8 75                 if (binCount >= TREEIFY_THRESHOLD) { 76                     treeifyBin(tab, i); 77                 } 78                 //putVal 的返回是添加前的旧值,所以返回 oldVal 79                 if (oldVal != null) { 80                     return oldVal; 81                 } 82                 break; 83             } 84         } 85     } 86     addCount(1L, binCount); 87     return null; 88 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdgzx.html