ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

ConcurrentHashMap源码分析

2021-12-29 22:01:29  阅读:175  来源: 互联网

标签:分析 Node ConcurrentHashMap hash key ek 源码 tab null


今天咱们就不大战秃头老了,给自己充充电,日后再战秃头老!

注:ConcurrentHashMap聊的是1.8之后的!

兄弟姐妹们都说HashMap线程不安全,想线程安全就用ConcurrentHashMap,那为什么它就线程安全那?啊?为什么呢!不用想,肯定从put()方法看起来嘛!那有请put()老哥,出来溜达一下呗!

// 哎呦,老面孔了,跟HashMap一样都调用了putVal方法
public V put(K key, V value) {
    return putVal(key, value, false);
}
// 哎呦,final修饰的方法,不允许被重写,看来很自信呀!
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 传递值非空校验
    if (key == null || value == null) throw new NullPointerException();
    // 通过 key 的 hashcode 求出 hash 值,即元素所在位置
    int hash = spread(key.hashCode());
    int binCount = 0;
    // 遍历table
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 判断是否需要初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 如果当前要put的位置为空,则通过 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;                   // no lock when adding to empty bin
        }
        // 是否需要扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 重头戏来咯,哎呦,原来加锁了
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        // 说明是链表,通过链表的方式添加元素
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    
                    else if (f instanceof TreeBin) {
                        // 红黑树,通过树的方式来添加元素
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                              value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 判断是否转换成红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

综上所述,就是加锁了!简单的描述下:

1:通过hashcode求出key应该所放的位置

2:是否已经初始化,如果没有则调用initTable()

3:当前位置的值是否为空,为空的则通过CAS乐观锁的方式添加元素(兄弟,别慌,后面会说CAS)

4:判断是否需要扩容

5:加synchronized来添加元素,如果是链表则遍历链表添加元素,如果是红黑树则调用红黑树添加元素的方法!


既然我们知道了为啥线程安全了,那我们顺带看一下initTable()方法呗,看不了上当,看不了吃亏!

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    // 是否需要初始化
    while ((tab = table) == null || tab.length == 0) {
        // sizeCtl 这个玩意老牛了
        // 如果 =  -1 说明有其他线程在初始化,
        // -N 说明N-1个线程在扩容
        // >0 代表容量
        if ((sc = sizeCtl) < 0)
            // 既然,有其他线程在初始化,那咱们就礼让一下呗!
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// 通过cas方式进行判断是否要初始化!(又是CAS,好奇不!)
            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,是不是说明 >0 代表容量
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

简单说就是,如果需要初始化,首先看看有没有其他线程在初始化,有就礼让,没有就让我来!特别注意sizeCtl!


看到两次CAS了,是不得说说,诶!先不说,先看看get(),这个函数很常用,顺带看下,按住躁动的心,get()非常简单!

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 求key所在的文职
    int h = spread(key.hashCode());
    // 是否已经初始化过了
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        // 指定元素的hash如果和头节点的hash相同,就直接返回头节点的值
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
    	// 如果头节点 hash小于0,则不是在扩容,就是红黑树
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 不是头,那就是链表,挨个遍历呗!
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

简单描述下:

1:通过key的hashcode求key的位置

2:判断是不是头节点,是就直接返回

3:如果头节点的hash小于0,不是在扩容,就是红黑树,通过find查找

4:最后就是链表了,遍历查找!


终于说完了ConcurrentHashMap,那是不是逼逼一下CAS那,害,咱们就放到下次充电呗!

嗯…想到看官老爷们,连个赞都不留下, 只有默默的阅读量,哎,看官老爷来个赞把!,下次咱们说说CAS,保证通俗易懂哦!(主要是我还没掌握透彻,等掌握透彻再来哦!)

参考文章:Guide哥的Java学习

标签:分析,Node,ConcurrentHashMap,hash,key,ek,源码,tab,null
来源: https://blog.csdn.net/weixin_42415173/article/details/122225483

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有