基于JDK1.8的ConcurrentHashMap分析

之前看过ConcurrentHashMap的分析,感觉也了解的七七八八了。但昨晚接到了面试,让我把所知道的ConcurrentHashMap全部说出来。

然后我结结巴巴,然后应该毫无意外的话就G了,今天下定决心好好分析一下,这个万能的并发包,ConcurrentHashMap

分一下几个方面分析ConcurrentHashMap:

put方法

remove方法

get方法

(一)put方法

public V put(K key, V value) {
    return putVal(key, value, false);
}

调用了putVal方法,传入三个参数。第一个为key,第二个为val,第三个为onlyIfAbsent(意思为: 如果为true,当插入的key相同时,不替换val值,默认是为false,替换最新的val值)

putVal方法比较多,我们分两个部分讲:

//第一部分
final V putVal(K key, V value, boolean onlyIfAbsent) {
    //对传入的参数进行合法性判断
    if (key == null || value == null) throw new NullPointerException();
    //计算键所对应的 hash 值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //如果哈希表还未初始化,那么初始化它
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //根据键的 hash 值找到哈希数组相应的索引位置
        //如果为空,那么以CAS无锁式向该位置添加一个节点
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                        new Node<K,V>(hash, key, value, null)))
                break;                 
        }

我们看到第四行,如果key和val都为null,直接抛出异常,所以不能传入key和val都不能为null。

第二个注意点是 散列这个函数

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

我在HashMap里面有介绍,我就不详细说了,反正很重要。

第三个注意点就是初始化table这个函数initTable

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    //如果表为空才进行初始化操作
    while ((tab = table) == null || tab.length == 0) {
        //sizeCtl 小于零说明已经有线程正在进行初始化操作
        //当前线程应该放弃 CPU 的使用
        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) {
                    //sc 大于零说明容量已经初始化了,否则使用默认容量
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    //根据容量构建数组
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    //计算阈值,等效于 n*0.75
                    sc = n - (n >>> 2);
                }
            } finally {
                //设置阈值
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

我们看到第7,8行。如果有线程在初始化,那么那就等待,让出cpu。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/e62e23a51fd49c42e74de7db91be3647.html