分析jdk 1.8 ConcurrentHashMap 的put 方法

如有转载请注明出处https://blog.csdn.net/weixin_41955327/article/details/90228701
public V put(K key, V value) {
//点击进入putVal
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//检查传入的参数是否规范, 注意这里的key 不同于HashMap 中可存储null 的key
    if (key == null || value == null) throw new NullPointerException();
// 计算key  的hash 值 用来后面定位元素
    int hash = spread(key.hashCode());
    int binCount = 0;
// table 引用指向的是ConcurrentHashMap中 所有元素所存在的数组的引用 所以下面依次将遍历
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
//如果tab是null 的那么在放入元素前先初始化,如何初始化,在下面解释
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
// 如果是初始化完毕  那么定位当前要put的元素应该放在table 中哪个下表位置上,使用方法tabAt  得到下标位置并且当前定位的下标
///位置上没有元素存储即为空,专业说当前下标没有发生hash冲突
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// casTabAt  方法是使用cas 将元素put 进去,put成功后break  循环
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
// 因为多线程操作下,可能其他线程正在进行此位置的节点链表的扩容,那么就帮助扩容
//多补充一下,ConcurrentHashMap 是可以多线程并发扩容,那么避免并发操作互相影响,有一个步长的概念,例如 我们的数据机构为20长度的数据,如果同时有四个线程并发,那么数组中平均每五个用一个线程来帮助他扩容,第一个线程可能区扩容数组中下表为0的,但是此线程在扩容下一个数组中的位置应该是下标为4的位置,刚好步长为5(依次0 4 8 12 16 20), 依次类推。第二个线程扩容 2 6 10 14 18.....
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
//开始锁住这个定位到的数组下标位置上的元素,开始在这个链表上插入  
            synchronized (f) {
//保持健壮性,再一次计算  数组中下表的位置,是否和上面f的位置相同
                if (tabAt(tab, i) == f) {
//fh 在上边条件判断时就已经赋值了 不要忘记,“ else if ((fh = f.hash) == MOVED)”  大于0的hash值证明是链表
                    if (fh >= 0) {
                        binCount = 1;
//遍历这个链表 每遍历一次binCount  会+1 操作,不要怀疑+1操作的原子性,因我们现在在锁内“ synchronized (f) {”
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
//要记住发生hash冲突才再数组上挂链表去解决这种问题,那么我们在put的时候就要知道,在这个链表上是挂着的每个元素(Node)是不是重复的在put key,所以在放入链表上的时候要一一遍历当前链表,如果真的重复就把value 插入
在这个条件判断上要知道一件事,如果key 的hash 值相同不能代表key 一定相同,但是key 的hash值不同一定代表key 不同,这个您百度一下hash 的定义很容易明白,所以hash 值比较后还需用equals 或者 ==  号 来判断是不是真的为同一个key
                            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;
//在这个条件判断中赋值e  为链表的下个元素,同时还判断是否为null  如果,为null  就说明已经是遍历到链表的最后一个位置上了
在这里还要说下,jdk 源码写的非常精炼,赋值和条件判断往往是写在一起的,让人看起来容易发蒙。。。。。
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
//如果 定位的当前的数组中元素   f  数红黑树,那么就按照红黑树结构来插入节点
                    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;
                        }
                    }
                }
            }
//要知道,当插入链表后值大于8 的时候节点应当 转红黑树局结构
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
//用来对加入的元素+1  为size 服务
    addCount(1L, binCount);
    return null;
}

// 解释如何初始化

//之所以在put时候初始化是因为在构造时没有初始化,只有真正用put的时候才会去初始化

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
//首先要知道在ConcurrentHashMap 中sizeCtl 这个常量值所代表的状态, 当sizeCtl =-1  时表示正在初始化 -n  当前有n-1个线程正在进行扩容, 0 表示当前的table 还没有被初始化,大于0表示初始化的大小而且也是一个阈值达到这个数量后需要扩容 
        if ((sc = sizeCtl) < 0)
//因为有个线程在扩容所以,我们当前的线程要礼让cpu 
            Thread.yield(); // lost initialization race; just spin
//这是一个cas 操作将当前的值赋值为-1 让其他线程知道有个线程正在扩容这个数组。
        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;
//  用数学角度来看  1 - 1/4 =0.75  即当数组的大小达到当前的75% 会再次扩容
                    sc = n - (n >>> 2);
                }
            } finally {
//  将当前扩后的的值赋给sizeCtl  让其作为下次扩容的阈值
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}
  • 感谢您的阅读。如果感觉文章对您有用,麻烦您动动手指点个赞,以资鼓励。谢谢!

如有转载请注明出处https://blog.csdn.net/weixin_41955327/article/details/90228701

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值