ConcurrentHashMap中sizeCtl的说明

  ConcurrentHashMap设计很强,其中sizeCtl设计十分巧妙。但是在网上查资料真的是以讹传讹啊。所以,我来写一下sizeCtl的说明。

  sizeCtl有多重含义,其中除了扩容的时候难理解外,其他的比较好理解

·    1  如果一个ConcurrentHashMap正在初始化,值为-1

   2  ConcurrentHashMap初始化完成正在使用,置为size * 0.75

  3 扩容的时候,是一个负值

  我们今天就来看看这个扩容的时候的值是怎么来的

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 (U.compareAndSwapInt(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 (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
            }
        }
    }

  如果第一个线程触发了扩容,那么注意这句话

U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2)
rs 的值是怎么来的呢
int rs = resizeStamp(n);
从方法名上看,这个值叫扩容标识戳。
我们就以n=16为例,
static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }
Integer.numberOfLeadingZeros(n)就不分析具体实现了,该方法的含义是一个数的最高位第一个非0开始算起,到他的最高位有多少个0。
比如n=16,那么 Integer.numberOfLeadingZeros(n)返回值就是27.
因为16是 10000。一个int是32位,32-5就是27.
(1 << (RESIZE_STAMP_BITS - 1)),其中 RESIZE_STAMP_BITS = 16。 也就是说该表达式的值是一个1,后面跟15个0.这里请注意,这个1的事情。
然后呢,两个值做或运算,那么刚才的1就会让这个返回的int值的低16位中的第 16位是1.

我们再看
U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2)
(rs << RESIZE_STAMP_SHIFT) + 2)

rs左移16位,还记得刚才说的1嘛,最高位是符号位,所以这个事肯定是一个负数。同时左移16位后,低16位就全部是0。然后加2.那么不看高16位,低16位的值就是2。
这也就是sizeCtl低16位表示的是参与扩容的线程数 + 1。

当最后一个扩容线程准备收尾的判断依据也是
sizeCtl-1 后是不是等于1。

以上就是sizeCtl的意义,总结下就是高16位是扩容标识戳,低16位是扩容线程数+1。

 

  

posted on 2020-10-19 10:44  MaXianZhe  阅读(1319)  评论(0编辑  收藏  举报

导航