美文网首页
ConcurrentHashMap源码导读之initTable(

ConcurrentHashMap源码导读之initTable(

作者: 犄角芝士 | 来源:发表于2018-11-11 22:48 被阅读0次

一、简介

ConcurrentHashMap在初始化table时调用的方法。方法用private final修饰。

二、源码

先介绍方法内的三个全局变量

  • sizeCtl
/**
 * 用于控制table初始化和resize的参数
 * -1表示初始化
 * 其他负数+1表示正在进行resize的线程数,为了与-1区别开
 * 0代表默认状态
 * 在初始化之后,该值表示下次需要resize时map内元素的个数
 */
private transient volatile int sizeCtl;
  • DEFAULT_CAPACITY
/**
 * 默认初始化table的容量,必须是2的n次方
 */
private static final int DEFAULT_CAPACITY = 16;

初始化ConcurrentHashMap是传入的容量不是2的n次方时会发生什么?

  • SIZECTL
// 初始化时与sizeCtl值相等,为0
private static final long SIZECTL;

static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

为什么SIZECTL需要通过反射来设置?

下面先贴出initTable()源码

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(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

可以看出table的初始化在一个cas方法中进行,当table为null或者长度为0时进入while方法。

进入之后判断sizeCtl的指,如果小于0则线程让步。由于初始状态sizeCtl是等于0的,说明前面已经有线程进入了elseif这部分,将sc的值置为-1,表示正在初始化。

如果sc大于0,则取sc,否则取默认容量16。然后计算下一次元素数量达到多少时需要resize。

为什么else..if代码块中还要判断一次table是否已经初始化

相关文章

网友评论

      本文标题:ConcurrentHashMap源码导读之initTable(

      本文链接:https://www.haomeiwen.com/subject/eewffqtx.html