栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JDK1.8 CourrentHashMap中addCount的小bug

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JDK1.8 CourrentHashMap中addCount的小bug

1. 扩容戳

每次扩容前,会调用resizeStamp函数以table容量为种子生成一个唯一的扩容戳。

static final int resizeStamp(int n) {
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
  	// Integer.numberOfLeadingZeros 表示参数n的二进制表示,从左边开始算起,连续为0的个数
}

在扩容时,sizeCtl变量会用来表示扩容状态,高16位保存着扩容戳,低16位保存着并发扩容的线程数

2.addCount方法分析

在putVal方法中,插入节点完毕后,调用addCount(1L, binCount),更新Map元素计数,其中含有扩容判断。Map元素总数超过阈值(0.75* table容量)则触发扩容。

扩容函数:

addCount()中扩容部分:

​<1>处代码分析:检验当前线程是否可以参与扩容

  1. (sc >>> RESIZE_STAMP_SHIFT) != rs

    如果不相等,说明table容量已经变化,扩容以及完成,此时sizeCtl为扩容阈值

  2. sc == rs + 1 (此处为jdk1.8中的bug,已在jdk12以后修改)

    用于判读扩容结束,(rs << 16) + 1表示扩容结束,+2表示第一个线程进行扩容,若第一个线程扩容结束,会讲rs进行-1操作,此时sc == (rs << 16) +1

  3. sc == rs + MAX_RESIZERS (此处为jdk1.8中的bug,已在jdk12以后修改)

    此初应该是sc == (rs << 16) + MAX_RESIZERS ,MAX_RESIZERS默认为( 1<<16 ) - 1

    扩容线程数量已达到最大值

  4. (nt = nextTable) == null

    此处表示nextTable还未完成初始化(只能有一个线程去完成)

  5. transferIndex <= 0

    表示需要迁移的桶已经被并发扩容线程瓜分完毕,无法继续帮助扩容

此处addCount以及helpTransfer中 int rs = resizeStamp(n); 赋值逻辑存在问题(JDK1.8中),rs获取的扩容戳为低16位数据,与sc进行比较时,需要左移16位才能正确判断之后的 sc == rs + 1以及 sc == rs + MAX_RESIZERS
该Bug已在JDK12中被修复,见下面的链接。
[Bug ID: JDK-8214427 probable bug in logic of ConcurrentHashMap.addCount()]
在stackoverflow中也找到了该问题的讨论。
stackoverflow.com

这个bug是2020年才在JDK12中修改的,对程序影响估计也很小,国内这么多公司都在用JDK1.8,也没出现什么大问题,因此也会很久没有修改过来吧,但是大家阅读源码过程中还是应该去看新版本JDK12以上的,此处已经修改过来了。

若要复现此bug,需要做到并发扩容线程达到最大值时进行判断,很难去模拟该情况(电脑跑到那么多线程确实也很离谱,默认情况下MAX_RESIZERS为 (1<< 16)-1 ),以及判断当前扩容是否结束,很难模拟一个线程刚好允许到此处,刚好遇到了sc == (rs << 16) +1 的情况。

JDK8中源码如下:

addCount方法:

    
//检查是否要进行扩容,插入元素在putVal方法中总会进行检查
        if (check >= 0) {
            Node[] tab, nt; int n, sc;
          	//此处sizeCtl表示的是扩容阈值,s的值为之前sumCount()计算得到的,表示当前总KV对个数
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&  
                   (n = tab.length) < MAXIMUM_CAPACITY) {  
              	//此处rs的赋值存在bug
              	int rs = resizeStamp(n);
                //int rs = resizeStamp(n) << RESIZE_STAMP_SHIFT; //生成当前容量n对应的扩容戳
                if (sc < 0) { // 并发扩容已经开始进行
                  // <1> flag
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0) // 检验当前线程是否可以参与扩容
                        break;
                  	// 通过检验,update sizectl -> sizectl+1,表示多了一个线程参与扩容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
              	// sc >= 0 说明当前容器还未进行并发扩容,运行此处的线程是第一个扩容线程
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2)) // rs << 16 + 1表示扩容结束
                  														// 此处+2 表示第一个线程进行扩容,更新sizeCtl变量从而修改扩容状态
                    transfer(tab, null);
                s = sumCount();
            }
        }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/288066.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号