ConcurrentHashMap 中竟有好几个BUG?JDK8 源码深度解析

阅读: 评论:0

ConcurrentHashMap 中竟有好几个BUG?JDK8 源码深度解析

ConcurrentHashMap 中竟有好几个BUG?JDK8 源码深度解析

文章目录

    • 哈希值是负数的几种特殊情况
    • sizeCtl
    • put
      • putVal
        • spread(int h) 求哈希值
        • initTable() 初始化数组
        • treeifyBin() 扩容或链表转红黑树
          • tryPresize(int size)
            • resizeStamp(int n) 计算扩容标识戳
            • transfer(tab, null) 第一个进来执行扩容的线程
            • lastRun 机制图示
        • helpTransfer 协助扩容
    • 源码中的 BUG 及修复版本
      • BUG 一:sizeCtl 临时变量 sc 判断条件
        • sc == rs + 1
        • sc == rs + MAX_RESIZERS
        • bug fix in jdk12
      • BUG 二:tryPresize(int size) 中 sc < 0 的判断
        • bug fix in jdk9
      • BUG 三:computeIfAbsent 导致死循环
        • computeIfAbsent In JDK 8
        • bug fix in jdk9

#八股文 #JUC

哈希值是负数的几种特殊情况

// 数组中的数据的哈希值是负数的特殊含义
static final int MOVED     = -1; // 数组正在扩容,并且当前位置的数据已经迁移到了新数组
static final int TREEBIN   = -2; // 当前索引位置上是红黑树
static final int RESERVED  = -3; // 当前索引位置已经被占了,但是值还没设置

sizeCtl

// sizeCtl > 0:若当前数组没有初始化,代表初始化的长度;若数组已初始化,代表下次扩容的阈值
// sizeCtl = 0:数组数还没初始化
// sizeCtl = -1:数组正在初始化
// sizeCtl < -1:数组正在扩容// 数组初始化和扩容的标识信息。
private transient volatile int sizeCtl;

put

// 若 key 已存在,则使用 value 覆盖 oldValue,并返回 oldValue
public V put(K key, V value) {return putVal(key, value, false);  
}// 若 key 已存在,什么都不做,并返回 oldValue
// absent:缺席、不到场
public V putIfAbsent(K key, V value) {  return putVal(key, value, true);  
}

putVal

put 时没有哈希冲突用 CAS,有哈希冲突用 synchronized。

    final V putVal(K key, V value, boolean onlyIfAbsent) {// key 和 value 不能为 null(HashMap key 和 value 可以为 null)if (key == null || value == null) throw new NullPointerException();// 根据 key 计算出 hashcodeint hash = spread(key.hashCode());int binCount = 0;// 死循环for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 初始化数组(HashMap 也是第一次 put 时初始化数组)if (tab == null || (n = tab.length) == 0)tab = initTable();// 下标为 i 的桶中的数据为 null 时,则以 CAS 的方式将 Node 放入桶中(不加锁)// CAS 操作失败就再走一次循环else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   }// static final int MOVED = -1; // hashcode = -1,当前位置的数据已经迁移到了新数组else if ((fh = f.hash) == MOVED)// 看看数组中是否还有没迁移的数据,帮助扩容tab = helpTransfer(tab, f);else {// 如果以上情况都不满足,说明出现哈希冲突则利用 synchronized 写入数据  V oldVal = null;// 锁住当前桶synchronized (f) {// 确认数据没有变化if (tabAt(tab, i) == f) {// 链表添加结点if (fh >= 0) {// 记录链表长度binCount = 1;// 死循环for (Node<K,V> e = f;; ++binCount) {K ek;// 从桶中结点开始遍历链表校验 key 是否相同if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;// onlyIfAbsent 为 false 才覆盖老数据if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;// 判断是否是链表最后一个结点if ((e = e.next) == null) {// 将当前结点挂在链表最后 = new Node<K,V>(hash, key,value, null);break;}}}// 红黑树添加结点else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {// static final int TREEIFY_THRESHOLD = 8;// 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树if (binCount >= TREEIFY_THRESHOLD)// 判断是扩容还是转红黑树treeifyBin(tab, i);if (oldVal != null)// 若 oldValue 有值,返回 oldValue。// 若 oldValue 没有值,返回 nullreturn oldVal;break;}}}addCount(1L, binCount);return null;}
spread(int h) 求哈希值
// HASH_BITS 的二进制表示: 0111 1111 1111 1111 1111 1111 1111 1111
// 除了首位为 0,其他位都为 1
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hashstatic final int spread(int h) {// h ^ (h >>> 16) 让 hashCode 的高 16 位参与到索引位置的计算中,从而减少哈希冲突// 将异或操作后的哈希值对 HASH_BITS 进行 & 运算是为了保证返回的哈希值一定是正数// 因为 CHM 中数组中的数据的哈希值如果是负数,有特殊含义return (h ^ (h >>> 16)) & HASH_BITS;  
}
initTable() 初始化数组
// 数组默认初始化长度
private static final int DEFAULT_CAPACITY = 16;// LOAD_FACTOR 是个 static final 修饰的常量
private static final float LOAD_FACTOR = 0.75f;private final Node<K,V>[] initTable() {  Node<K,V>[] tab; int sc;  while ((tab = table) == null || tab.length == 0) {// 数组正在初始化if ((sc = sizeCtl) < 0) Thread.yield(); // 让出 CPU 的时间片// 数组还未初始化,以 CAS 的方式将 sc 修改为 -1,代表数组正在初始化else if (UpareAndSwapInt(this, SIZECTL, sc, -1)) {  try {// DCL(Double Check Lock)if ((tab = table) == null || tab.length == 0) {// 默认初始化长度 16 int n = (sc > 0) ? sc : DEFAULT_CAPACITY;  // Node 数组初始化完成Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];  // 依次给局部变量和成员变量赋值table = tab = nt;  // 计算下次扩容的阈值。LOAD_FACTOR = 0.75 是个常量。// n * (1 - 1/4) = 0.75 * n;sc = n - (n >>> 2);  }  } finally {// 局部变量 sc 赋值给成员变量 sizeCtlsizeCtl = sc;  }  break;  }  }    return tab;  
}
treeifyBin() 扩容或链表转红黑树
private final void treeifyBin(Node<K,V>[] tab, int index) {  Node<K,V> b; int n, sc;  if (tab != null) {  // static final int MIN_TREEIFY_CAPACITY = 64;// 若数组长度小于 64,直接扩容if ((n = tab.length) < MIN_TREEIFY_CAPACITY)  // 扩容前的一些准备的校验tryPresize(n << 1);  // 链表转红黑树// 将单向链表转化为 TreeNode 对象(双向链表),再通过 TreeBin 方法转为红黑树// TreeBin 中保留着双向链表和红黑树else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {synchronized (b) {if (tabAt(tab, index) == b) {TreeNode<K,V> hd = null, tl = null;for (Node<K,V> e = b; e != null; e = e.next) {TreeNode<K,V> p =new TreeNode<K,V>(e.hash, e.key, e.val,null, null);if ((p.prev = tl) == null)hd =  = p;tl = p;}setTabAt(tab, index, new TreeBin<K,V>(hd));}}}}}
}
tryPresize(int size)

扩容标识戳

    private static final int MAXIMUM_CAPACITY = 1 << 30;private static int RESIZE_STAMP_BITS = 16;private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;// 00000000 00000000 11111111 11111111private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;// treeifyBin 中调用该方法时,size 是数组长度的 2 倍// putAll 中调用该方法时,size 是插入 map 的 sizeprivate final void tryPresize(int size) { // 初始化数组的长度int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :tableSizeFor(size + (size >>> 1) + 1);int sc;while ((sc = sizeCtl) >= 0) {// n : 数组长度Node<K,V>[] tab = table; int n;// 初始化数组if (tab == null || (n = tab.length) == 0) {n = (sc > c) ? sc : c;// 以 CAS 的方式将 sc 修改为 -1if (UpareAndSwapInt(this, SIZECTL, sc, -1)) {try {// DCLif (table == tab) {Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}}}// c 没有超过阈值或者 数组长度大于等于 MAXIMUM_CAPACITYelse if (c <= sc || n >= MAXIMUM_CAPACITY)break;// 确认 table 没有被其他线程修改过else if (tab == table) {// 计算扩容标识戳int rs = resizeStamp(n);// bug之1:sc 是局部变量(sc = sizeCtl),sc 大于等于 0 才能进入 while 循环// 由于之前已经判断过初始化了,所以这里必是扩容if (sc < 0) {Node<K,V>[] nt;if ((sc >>> RESIZE_STAMP_SHIFT) != rs || // 判断协助扩容的标识戳是否一致(sc 的高 16 位扩容标识戳 rs)sc == rs + 1 || // bug之2 这里应该是 sc == rs << RESIZE_STAMP_SHIFT + 1 判断扩容操作是否已经达到了最后的检查阶段(sc 的高 16 和 rs 相同,如果 sc 和 rs << 16 + 1 相等,则说明 sc 的低 16 位等于 1。)// MAX_RESIZERS 是 1 左移 16 位减 1 即 低 16 位都是 1sc == rs + MAX_RESIZERS || // bug之三,这里应该是 sc == rs << RESIZE_STAMP_SHIFT + MAX_RESIZERS 判断扩容线程是否已经达到最大值(nt = nextTable) == null || // 新数组为 null,说明扩容完毕,因为扩容完毕以后会将 nextTable 置为 nulltransferIndex <= 0) // transferIndex 为线程领取任务的最大结点,如果为0,代表所有老数据的迁移任务都被领完了break;if (UpareAndSwapInt(this, SIZECTL, sc, sc + 1))transfer(tab, nt);}// 第一个进来执行扩容的线程// 基于 CAS 的方式将 sizeCtl 从修改为 rs 左移 16 位 + 2// sizeCtl : 10000000 00011010 00000000 00000010 代表三重含义// 1. 10000000 00011010 00000000 00000010 < -1,代表正在扩容// 2. 低 16 位代表正在扩容的线程数+1,此时低 16 位为 2,表示有 1 个线程正在扩容// 3. 高 16 位代表标识戳 rs  // 这么做的原因是最后一个迁移完数据的线程需要检查一遍有没有数据漏掉// 因为扩容分为两步,创建新数组并迁移数据// 当最后一个线程迁移完毕数据后,对低位 -1,最终结果低位还是 1,此时这个线程需要对整个老数组再次检查,数据是否迁移干净 recheck before commitelse if (UpareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))// 开始扩容,传入老数组transfer(tab, null);}}}
resizeStamp(int n) 计算扩容标识戳

基于数组长度得到一个标识,在其他线程帮助扩容时,需要校验该标识,标识一致才可以帮助扩容。

private static int RESIZE_STAMP_BITS = 16;// Integer.numberOfLeadingZeros(n) 返回 n 用二进制表示时,前置 0 的个数
// 1 << (RESIZE_STAMP_BITS - 1) 表示 1 左移 15 位,00000000 00000000 10000000 00000000。
// Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)) 中的 '|' 操作是为了保证当前扩容标识戳左移 16 位后一定是负数(最高位为 1)。// 为什么要保证当前扩容标识戳左移 16 位后一定是负数?
// 跟 跟sizeCtl 有关,sizeCtl 是负数代表正在扩容。// 假设数组长度 n=32,二进制为:0b 00000000 00000000 00000000 00100000
// Integer.numberOfLeadingZeros(32) = 26
// 26 的二进制为:0b 00000000 00000000 00000000 00011010
// 00000000 00000000 00000000 00011010 | 00000000 00000000 10000000 00000000
// 00000000 00000000 10000000 00011010
static final int resizeStamp(int n) {  return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));  
}
transfer(tab, null) 第一个进来执行扩容的线程
  1. 计算步长 stride
  2. 初始化新数组
  3. 线程领取迁移数据任务
  4. 判断迁移是否结束
  5. 查看当前位置数据是否为 null
  6. 查看当前位置数据是否为 fwd
  7. 链表迁移数据 lastRun 机制
  8. 红黑树迁移 迁移完数据长度若小于等于 6,则转回链表
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {  // stride 迁移数据的步长int n = tab.length, stride;  // static final int NCPU = Runtime().availableProcessors();// private static final int MIN_TRANSFER_STRIDE = 16;// stride = (NCPU > 1) ? (n >>> 3) / NCPU : n// if(stride < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE;// 若 cpu 核数 > 1,就计算每个 cpu 需要迁移的数组的下标的长度,计算公式为 n / 8 / NCPU// 若计算得出的 stride < 16,就用 16,表示每个线程每次最少迁移16长度的数据if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)  stride = MIN_TRANSFER_STRIDE; // 初始化新数组if (nextTab == null) {            try {  Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];  nextTab = nt;  } catch (Throwable ex) {      // try to cope with OOME  // OOM or 数组长度溢出sizeCtl = Integer.MAX_VALUE;  return;  }  // 将 nextTab 赋值给成员变量 nextTablenextTable = nextTab;  // transferIndex 设置为老数组的长度 transferIndex = n;  }// int nextn = nextTab.length;  ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);  boolean advance = true;// 扩容是否结束boolean finishing = false; // to ensure sweep before committing nextTab// 死循环扩容  for (int i = 0, bound = 0;;) {  Node<K,V> f; int fh;  while (advance) {  int nextIndex, nextBound;  if (--i >= bound || finishing)  advance = false;// 说明没有任务可以领取了// 此处将 transferIndex 赋值给 nextIndexelse if ((nextIndex = transferIndex) <= 0) {// 把 i 赋值为 -1,以便进入 if (i < 0 || i >= n || i + n >= nextn)i = -1;  advance = false;  }// 开始领取任务// 基于 CAS 的方式将 nextIndex(transferIndex) 的值更新为 nextBoundelse if (UpareAndSwapInt  (this, TRANSFERINDEX, nextIndex,  nextBound = (nextIndex > stride ?  nextIndex - stride : 0))) {  bound = nextBound; // 老数组长度 -1,nextIndex 就是 transferIndex 更新前的值i = nextIndex - 1;// 领取任务成功退出循环,迁移 i~bound 之间的任务// 举个例子 老数组长度 32,步长 stride 为 16,第一个线程领取到的任务就是32~16,换成数组下标就是31~16,其他线程可能领取到的就是 15~0 的数据的迁移任务。advance = false;  }  }// 迁移完成或没有任务可以领取if (i < 0 || i >= n || i + n >= nextn) {  int sc;  if (finishing) {  // 结束扩容,将 nextTable 设置为 nullnextTable = null;  // 将迁移完数据的新数组指向老数组table = nextTab;  // 将 sc 赋值为下次扩容的阈值 2 * n - n/2 = 1.5 * nsizeCtl = (n << 1) - (n >>> 1);  return;  }// 如果一个线程完成了迁移,尝试再去领取任务,若 transferIndex <= 0,所有任务已经被其他线程领走了,则把 i 设置为 -1。再执行退出扩容的操作(sc - 1),退出扩容成功后,判断当前线程是否最后一个退出扩容的,若是,则做检查操作。// 基于 CAS 的方式,将 sc 低位 -1,代表当前线程退出扩容if (UpareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {  // 判断是否是最后一个结束迁移数据的线程if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)  return;  finishing = advance = true;// 检查老数组是否迁移完成  i = n; // recheck before commit}  }else if ((f = tabAt(tab, i)) == null) // 如果迁移位置的数据为 null,则放置一个 fwd,表示数据已经迁移(最后一个线程检查时走的逻辑)advance = casTabAt(tab, i, null, fwd);  else if ((fh = f.hash) == MOVED)  // 最后一个线程检查时走的逻辑advance = true; // already processed  else {synchronized (f) {if (tabAt(tab, i) == f) {Node<K,V> ln, hn;if (fh >= 0) {int runBit = fh & n;Node<K,V> lastRun = f;// lastRun 机制// 提前循环一次链表,将结点赋值到对应的高低位 Node// 便于迁移最后几个都是迁移到高位或者低位的结点// 直接将这几个的头结点迁移过去就好了for (Node<K,V> p = f.next; p != null; p = p.next) {int b = p.hash & n;if (b != runBit) {runBit = b;lastRun = p;}}if (runBit == 0) {ln = lastRun;hn = null;}else {hn = lastRun;ln = null;}// 再次循环时,就循环到 lastRun 位置,不用继续往下循环了// 这样就不用每个结点都重新创建,节约资源,提升效率for (Node<K,V> p = f; p != lastRun; p = p.next) {int ph = p.hash; K pk = p.key; V pv = p.val;if ((ph & n) == 0)ln = new Node<K,V>(ph, pk, pv, ln);elsehn = new Node<K,V>(ph, pk, pv, hn);}// 放低位setTabAt(nextTab, i, ln);// 放高位setTabAt(nextTab, i + n, hn);// 将当前桶位置设置为 fwd,代表数据已迁移setTabAt(tab, i, fwd);// 执行下次循环advance = true;}else if (f instanceof TreeBin) {TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> lo = null, loTail = null;TreeNode<K,V> hi = null, hiTail = null;int lc = 0, hc = 0;for (Node<K,V> e = t.first; e != null; e = e.next) {int h = e.hash;TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null);if ((h & n) == 0) {if ((p.prev = loTail) == null)lo =  = p;loTail = p;++lc;}else {if ((p.prev = hiTail) == null)hi =  = p;hiTail = p;++hc;}}ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :(hc != 0) ? new TreeBin<K,V>(lo) : t;hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :(lc != 0) ? new TreeBin<K,V>(hi) : t;setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}}}}
lastRun 机制图示

迁移前:
![[Pasted image 20220901204510.png]]
迁移后:
![[Pasted image 20220901204544.png]]

helpTransfer 协助扩容
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {  Node<K,V>[] nextTab; int sc;  // 老数组不为 null,当前结点是 fwd, 新数组也不为 nullif (tab != null && (f instanceof ForwardingNode) &&  (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {  // 创建扩容标识戳int rs = resizeStamp(tab.length);  // DCLwhile (nextTab == nextTable && table == tab &&  (sc = sizeCtl) < 0) {  if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||  sc == rs + MAX_RESIZERS || transferIndex <= 0)  // 上述条件有一个满足就不需要协助扩容了 break// sc == rs + 1 || sc == rs + MAX_RESIZERS 这里 bug 同上break;  // CAS sc+1 协助扩容if (UpareAndSwapInt(this, SIZECTL, sc, sc + 1)) {  transfer(tab, nextTab);  break;  }  }        return nextTab;  }  return table;  
}

源码中的 BUG 及修复版本

不要以为源码中都是对的,然后强行解释。 - 我说的

BUG 一:sizeCtl 临时变量 sc 判断条件

此 BUG 影响不大,在 JDK 12 中修复。

 int rs = resizeStamp(n);if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||  sc == rs + MAX_RESIZERS || transferIndex <= 0)  

上述这个判断条件中源码中出现多次,有问题的下面两个条件:

sc == rs + 1 || sc == rs + MAX_RESIZERS

sc 的低 16 位代表正在扩容的线程数+1,此时低 16 位为 2,表示有 1 个线程正在扩容。高 16 位代表标识戳 rs。具体推断逻辑看上文源码分析。

sc == rs + 1

sc == rs + 1 是为了判断扩容操作是否已经达到了最后的检查阶段。
正确的写法应该是 sc == rs << RESIZE_STAMP_SHIFT + 1。因为 sc 的高 16 代表 rs,如果 sc 和 rs << RESIZE_STAMP_SHIFT + 1 相等,则说明 sc 的低 16 位(正在扩容的线程数)等于 1,即到了最后的检查阶段。

sc == rs + MAX_RESIZERS

private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
MAX_RESIZERS 是 1 左移 16 位减 1 即 低 16 位都是 1。
sc == rs + MAX_RESIZERS 是为了判断扩容线程是否已经达到最大值。
正确的写法应该是应该是 sc == rs << RESIZE_STAMP_SHIFT + MAX_RESIZERS

bug fix in jdk12
            int rs = resizeStamp(n) << RESIZE_STAMP_SHIFT;if (sc == rs + MAX_RESIZERS || sc == rs + 1 ||(nt = nextTable) == null || transferIndex <= 0)break;

JDK12 中将 resizeStamp(n) 的返回值左移了 16 位以后再赋值给 rs。同时 (sc >>> RESIZE_STAMP_SHIFT) != rs 也改成了 sc == rs + MAX_RESIZERS。

BUG 二:tryPresize(int size) 中 sc < 0 的判断

        // 将 sizeCtl 赋值给局部变量 scwhile ((sc = sizeCtl) >= 0) {Node<K,V>[] tab = table; int n;if (tab == null || (n = tab.length) == 0) {n = (sc > c) ? sc : c;if (UpareAndSwapInt(this, SIZECTL, sc, -1)) {try {if (table == tab) {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}}}else if (c <= sc || n >= MAXIMUM_CAPACITY)break;else if (tab == table) {int rs = resizeStamp(n);if (sc < 0) {Node<K,V>[] nt;if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0)break;if (UpareAndSwapInt(this, SIZECTL, sc, sc + 1))transfer(tab, nt);}else if (UpareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);}}

上述 sc < 0 的判断条件永远不可能进入,因为 sc 是 while 循环中的局部变量。sc >= 0,才会进入 while 循环。如果执行到 sc < 0,则说明之前没有对 sc 做赋值操作。所以此时 sc 肯定是大于等于 0 的。

bug fix in jdk9
private final void tryPresize(int size) {int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :tableSizeFor(size + (size >>> 1) + 1);int sc;while ((sc = sizeCtl) >= 0) {Node<K,V>[] tab = table; int n;if (tab == null || (n = tab.length) == 0) {n = (sc > c) ? sc : c;if (UpareAndSetInt(this, SIZECTL, sc, -1)) {try {if (table == tab) {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}}}else if (c <= sc || n >= MAXIMUM_CAPACITY)break;else if (tab == table) {int rs = resizeStamp(n);// 主要看这里,已经不校验 sc < 0 了if (UpareAndSetInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);}}}

BUG 三:computeIfAbsent 导致死循环

内含 Doug Lea 的回复
此 BUG 在 JDK 9 中被 Doug Lea 老爷子修复。

//  中的 demo
public class Test {  public static void main(String[] args) {  Map<String, Integer> map = new ConcurrentHashMap<>(16);  mapputeIfAbsent(  "AaAa",  key -> mapputeIfAbsent(  "BBBB",  key2 -> 42)  );  }  
}

该方法的意思是:当前 Map 中 key 对应的 value 不存在时,会调用 mappingFunction 函数,并且将该函数的执行结果(不为 null)作为该 key 的 value 返回。

computeIfAbsent In JDK 8
	// 上文中分析过的代码此处就不再赘述了public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {if (key == null || mappingFunction == null)throw new NullPointerException();int h = spread(key.hashCode());V val = null;int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {Node<K,V> r = new ReservationNode<K,V>();synchronized (r) {// 使用 ReservationNode 占位if (casTabAt(tab, i, null, r)) {binCount = 1;Node<K,V> node = null;try {// 使用传入的函数 mappingFunction 计算 valueif ((val = mappingFunction.apply(key)) != null)node = new Node<K,V>(h, key, val, null);} finally {// 将占位的 r 替换成上面的 nodesetTabAt(tab, i, node);}}}if (binCount != 0)break;}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {boolean added = false;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek; V ev;if (e.hash == h &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {val = e.val;break;}Node<K,V> pred = e;if ((e = e.next) == null) {if ((val = mappingFunction.apply(key)) != null) {added =  = new Node<K,V>(h, key, val, null);}break;}}}else if (f instanceof TreeBin) {binCount = 2;TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> r, p;if ((r = t.root) != null &&(p = r.findTreeNode(h, key, null)) != null)val = p.val;else if ((val = mappingFunction.apply(key)) != null) {added = true;t.putTreeVal(h, key, val);}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (!added)return val;break;}}}if (val != null)addCount(1L, binCount);return val;}
bug fix in jdk9
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {if (key == null || mappingFunction == null)throw new NullPointerException();int h = spread(key.hashCode());V val = null;int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh; K fk; V fv;if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {Node<K,V> r = new ReservationNode<K,V>();synchronized (r) {if (casTabAt(tab, i, null, r)) {binCount = 1;Node<K,V> node = null;try {if ((val = mappingFunction.apply(key)) != null)node = new Node<K,V>(h, key, val);} finally {setTabAt(tab, i, node);}}}if (binCount != 0)break;}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else if (fh == h &&                  // check first node((fk = f.key) == key || fk != null && key.equals(fk)) &&(fv = f.val) != null)return fv;else {boolean added = false;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == h &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {val = e.val;break;}Node<K,V> pred = e;if ((e = e.next) == null) {if ((val = mappingFunction.apply(key)) != null) {if ( != null)throw new IllegalStateException("Recursive update");added =  = new Node<K,V>(h, key, val);}break;}}}else if (f instanceof TreeBin) {binCount = 2;TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> r, p;if ((r = t.root) != null &&(p = r.findTreeNode(h, key, null)) != null)val = p.val;else if ((val = mappingFunction.apply(key)) != null) {added = true;t.putTreeVal(h, key, val);}}// bug fixelse if (f instanceof ReservationNode)throw new IllegalStateException("Recursive update");}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (!added)return val;break;}}}if (val != null)addCount(1L, binCount);return val;}

本文发布于:2024-01-29 00:23:01,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170645898211375.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23