You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 8 ConcurrentHashMap中sizeCtl低16位为何从1而非0统计扩容线程数?

ConcurrentHashMap sizeCtl低16位从1开始计数的设计原因

首先纠正一个常见误解:低16位从1开始计数不是为了给初始化状态-1预留空间。因为初始化状态的-1是全1的二进制值,而扩容状态的sizeCtl高16位是由resizeStamp(n)生成的固定标记(不会是全1),所以无论低16位是什么值,扩容状态的sizeCtl都不可能等于-1。

真正的设计原因和扩容流程的逻辑一致性有关:

  • 扩容线程的计数逻辑:
    启动扩容时,sizeCtl被设置为(rs << RESIZE_STAMP_SHIFT) + 2,这里的+2对应1个活跃扩容线程(线程数 = 低16位值 - 1)。后续每有一个新线程加入扩容,就会通过CAS把sizeCtl加1(线程数同步+1);每有一个线程完成扩容任务,就会把sizeCtl减1(线程数同步-1)。

  • 扩容完成的判断依据:
    当sizeCtl减到(rs << RESIZE_STAMP_SHIFT) + 1时,低16位值为1,对应线程数为0,说明已经没有活跃的扩容线程了。此时最后一个完成任务的线程会触发扩容收尾工作:将sizeCtl设置为新表的容量阈值(新容量 * 0.75)。
    如果低16位从0开始计数,初始启动时sizeCtl会是(rs<<16)+1,线程完成后会减到(rs<<16)+0,虽然也能判断扩容结束,但这样的设计会让“扩容中”(线程数>0)和“扩容结束”(线程数=0)的状态区分不够直观,且需要调整后续的判断逻辑。

  • 源码逻辑的自洽性:
    源码中的判断条件sc == rs + 1(实际等价于sc == (rs << RESIZE_STAMP_SHIFT) + 1,rs是resizeStamp(n)的返回值,高16位为0,左移16位后才是扩容标记的高16位),就是用来判断当前是否已无活跃扩容线程,从而终止当前线程的扩容尝试。如果低16位从0开始计数,这个条件就要改成sc == rs,会破坏现有逻辑的简洁性。

简单来说,这种设计是为了让扩容线程的增减、扩容完成的判断逻辑更清晰自洽,避免不必要的状态混淆。

相关源码片段:

if (check >= 0) {
    Node<K,V>[] tab, nt; int n, sc;
    while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
           (n = tab.length) < MAXIMUM_CAPACITY) {
        int rs = resizeStamp(n);
        if (sc < 0) {
            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))
            // Start resizing
            transfer(tab, null);
        s = sumCount();
    }
}

内容的提问来源于stack exchange,提问作者user26615121

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:57:31