• 源码阅读


    去年底重撸了部分 ConcurrentHashMap 源码,当时笔记为 word 形式,比较乱,且刚好当时入职了一家新公司,整理这部分就停下来了(源码学习这部分在大部分公司里都会没时间去做,时间全靠挤)。刚好最近读完部分 redis 内部数据结构实现(虽然 C 语言不是很懂,但应该还是读懂了重要的部分),正好与Java 这边的 ConcurrentHashMap 形成对比,CHM 扩容这块的源码之后重新整理下就会发上来。

    为了见证自己曾经撸过 CHM 源码,并且发现代码的怪异之处,现在将一些“证据”整理发上来,作为后续 CHM 源码学习笔记的一个起点......

    addCount 方法中出 bug 的代码一如下:

     1         if (check >= 0) {
     2             Node<K,V>[] tab, nt; int n, sc;
     3             while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
     4                    (n = tab.length) < MAXIMUM_CAPACITY) {
     5                 int rs = resizeStamp(n);
     6                 if (sc < 0) {
     7                     if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
     8                         sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
     9                         transferIndex <= 0)
    10                         break;
    11                     if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
    12                         transfer(tab, nt);
    13                 }
    14                 else if (U.compareAndSwapInt(this, SIZECTL, sc,
    15                                              (rs << RESIZE_STAMP_SHIFT) + 2))
    16                     transfer(tab, null);
    17                 s = sumCount();
    18             }
    19         }

     出 bug 的代码二定位于 if (sc < 0) 分支下的第一个 if 条件:

    1                     if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
    2                         sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
    3                         transferIndex <= 0)
    4                         break;

    rs = resizeStamp(n);  这个方法的作用就是对散列表的数组的长度进行标记,其输出如下:

    resize stamp when n=0 : 32800
    resize stamp when n=1 : 32799
    resize stamp when n=2 : 32798
    resize stamp when n=3 : 32798
    resize stamp when n=4 : 32797
    resize stamp when n=5 : 32797
    resize stamp when n=6 : 32797
    resize stamp when n=7 : 32797
    resize stamp when n=8 : 32796
    resize stamp when n=16 : 32795
    resize stamp when n=32 : 32794
    resize stamp when n=1<<30 : 32769
    resize stamp when n=1<<31 : 32768
    resize stamp when n=1<<32 : 32799
    resize stamp when n=1<<33 : 32798

    可以发现 resizeStamp 方法的输出是固定在 32768~32798 之间的值(n=0和1对应的32799、32800都不会是CHM内部数组的长度值)

    32768 的二进制为: 1000 0000 0000 0000,1 个 1 后面跟着 15 个 0,总计 16 位

    我们取 n = 4 来分析(相当于创建CHM时指定了 initialCap),n=4 时 rs = 32797 = 32768 + 29, 对应二进制为:1000 0000 0001 1101

     在“代码一”中有一个 else if 代码分支,其对应的是第一个进入扩容的线程执行的操作:第一条线程扩容时,首先将原本是正数的 sizeCtl,修改为负数,对应操作:

    U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2)
    RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS = 16,


    CAS操作后 sizeCtl = 1000 0000 0001 1101 0000 0000 0000 0010,对应十进制为:-2145583102(32797<<16=-2145583104),总之肯定是个负数,且 sizeCtl 为负数时其高 16 位保存的是扩容前数组的 sizeStamp。

    将出bug的代码二再贴一遍:

                        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)

    能进入上述代码,sc < 0,rs 是正数且取值范围为 32768~32798,因此不可能出现上述代码中的“sc == rs + 1” 和 “sc == rs + MAX_RESIZERS”(注:MAX_RESIZERS=65535,是帮助扩容的最大线程数限制)。

    而前几行我们分析过 “sizeCtl 为负数时其高 16 位保存的是扩容前数组的 sizeStamp”,因此这两处 bug 正确写法应该是 sc == rs<<16 + 1 和 sc == rs<<16 + MAX_RESIZERS

    当时比较有意思的是,在参考 https://juejin.im/post/5b001639f265da0b8f62d0f8#comment 这篇 CHM 总结文章分析到 addCount 时发现这个疑虑,搜索 stackoverflow 时有人也提出了同样的问题,但这位同学更进了一步,直接向 JDK 提出了 BUG,JDK bug link:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8214427。最后这个 BUG 被 JDK 收录。详情可以在 bug link 中查看。

    这位同学提出 bug 的同时,对 bug 的纠正用的是 <<< ,而我在这篇文章中用的是 <<,写的时候突然意识到这一点不同,用编译器(Intellij Idea)验证了一下,java 中没有 <<< 这个符号,只有 >>> 。不过这点倒是无伤大雅,整体的思路对了我觉得就达到目的了,一些小细节的地方硬记它有还是没有作用不大。而且你只需要有一款像我一样的编译器,就能提示你 <<< 是错误的,这已经能解决问题。

    好了,今天这个 bug 的分析到此到一段落,后续会将 CHM 几个重要方法的分析贴上来,不过我应该还是会继续用脑图导出的图片的方式,可能是一个方法做一张图,内容的框架和鲁道大佬总结的这篇 CHM 应该差不多:https://juejin.im/post/5b001639f265da0b8f62d0f8#comment,没学习的小伙伴赶紧去看一下吧~

  • 相关阅读:
    文本信息检索基本知识【转】
    单纯形【转】
    云飘忽不定?且看多种多样的云存储应用【转】
    海量数据来临 分层存储才是解决之道【转】
    伽玛函数_gamma
    设计模式理解创建模式
    Memory management demo from TCPL
    Welcome to asm!
    Monokai scheme for Qt
    Astyle for qtcreator
  • 原文地址:https://www.cnblogs.com/christmad/p/11385863.html
Copyright © 2020-2023  润新知