ConcurrentHashMap第二讲:Put源码分析(重点)

在这里插入图片描述

false 的意思是:如果遇到key一样的,就替换value
如果是true:遇到key一样的 就无法写入数据,无法替换

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

putVal中会用到的方法: initTable()

initTable 是用来初始化table的。

  *   sizeCtl < 0
     *  1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
     *  2. 表示当前mao正在进行扩容 高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
     *
     *   sizeCtl = 0
     *   表示创建table数组时,使用 DEFAULT_CAPACITY 为大小
     *
     *   sizeCtl > 0
     *   1.如果table 未初始化,表示初始化大小
     *   2.如果已经初始化,表示下次扩容时的 触发条件(阈值)
     */
    private final Node<K,V>[] initTable() {
        / tab:table引用
        / sc:表示临时局部的sizeCtl 的值
        Node<K,V>[] tab; int sc;

        / 自旋 条件是table 是null,当前散列表尚未初始化
        while ((tab = table) == null || tab.length == 0) {

            if ((sc = sizeCtl) < 0)
                / -1 其他线程正在初始化操作,当前线程没有竞争到初始化table的锁
                Thread.yield(); // lost initialization race; just spin
            / 1.sizeCtl = 0   创建table数组时,使用 DEFAULT_CAPACITY 为大小
            / 2.sizeCtl > 0   如果table 未初始化,表示初始化大小
            / 3.sizeCtl > 0   如果已经初始化,表示下次扩容时的 触发条件(阈值)

            /sizeCtl的所在地址上的值 和 sc 比较,如果一样,就设置为 -1 代表拿到这把锁
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    /为什么又要判断一次呢?
                    / 防止本线程执行上个if与这个if之间,其它线程以非常快递速度初始化了table,
                    / 并且添加数据,如果回到本线程后再次初始化就会覆盖,导致丢失数据
                    if ((tab = table) == null || tab.length == 0) {

                        / sc大于0 创建table ,使用sc指定的大小,否则使用16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        / 赋值给map.table
                        table = tab = nt;
                        /下次扩容的阈值 n - n*(1/4) = n*0.75
                        / 默认:  16 - 16/4 = 12
                        sc = n - (n >>> 2);
                    }
                } finally {
                    / 1、如果当前线程是第一次创建(初始化)map.table的话,sc表示下一次扩容的阈值
                    / 2、表示当前线程不是第一次创建table的线程,当前线程进入到else if块时,
                    / 将SIZECTL位置上的sizeCtl 设置为了 -1.那么这是需要将其修改为 进入时的值
                    / 如果没有进入上面的if,sc的值应该还是else if 中的值,所有就是把sizeCtl的值由-1改回去
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

putVal方法

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        / key和 value 不能为空
        if (key == null || value == null) throw new NullPointerException();

        /让hash值的 高16位也参与到寻址运算中   扰动一下:更散一些
        int hash = spread(key.hashCode());
        / binCount:0:该key-value 插入到桶位中
        / 大于 0:key-value 插入到桶位后所属链表的下标位置
        / 等于 2:表示当前桶位已经树化
        int binCount = 0;
        / for 循环自旋
        / table 引用对象
        for (Node<K,V>[] tab = table;;) {
            / f: 表示桶位的头节点
            / n:散列表数组的长度,i:key寻址计算后,得到的桶位下标,fh:桶位头节点的hash值
            Node<K,V> f; int n, i, fh;

            /CASE1: 成立表示:tab 尚未初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            /CASE2:指定桶位为空就把key-value 放进去
            / tabAt 获取数组指定下标的元素
            / (n - 1) & hash : 该key.hashCode 扰动后所计算出的桶位下标
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                / 进入CASE2 代码块 前置条件,当前table 数组的i桶位是null
                / 使用CAS 方式 设置指定数组i桶位 为new Node<K,V>(hash, key, value, null),并且期望值是null
                /cas 操作成功 表示ok,直接break for即可
                / cas操作失败 表示其它线程抢先一步设置了值
                / 当前逻辑再次自旋 ,走其它逻辑
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            /CASE3:前置条件:桶位不为空
            / 当前桶位是FWD节点,表示map正处于扩容中
            else if ((fh = f.hash) == MOVED)
                //看到fwd 节点后,当前节点有义务帮助当前map 对象完成迁移数据的工作
                /学完扩容后再看
                tab = helpTransfer(tab, f);
            /CASE4:当前桶位可能是普通Node节点,可能是TreeBin节点
            else {
                /当插入key存在是,会将旧值赋值给oldVal,返回给put方法调用处
                V oldVal = null;
                / 使用sync 加锁“头节点”,理论上是“头节点”
                synchronized (f) {
                    / 为什么又要对比一下,看看当前桶位里面的头节点 是否为之前f获取的头节点
                    / (因为其它线程可能在该线程拿到f后 修改f,那样的话我们的锁就加错了,所以上面说理论上是“头节点”)
                    if (tabAt(tab, i) == f) {   / 条件成立 说明我们加锁没有问题
                        /条件成立:普通链表值 hash > 0
                        **    当node节点的 hash值 为 -1 时,表示当前节点是FWD节点
                         *     static final int MOVED = -1; // hash for forwarding nodes
                         *     当node节点的 hash值 为 -2 时,表示当前节点已经树化,且当前节点为TreeBin对象,TreeBin对象代理操作红黑树
                         *     static final int TREEBIN   = -2; // hash for roots of trees
                         */
                        if (fh >= 0) {
                            / 1.当前插入key 与链表当中所有元素的key都不一致时,当前的插入操作时最佳到链表的末尾,binCount代表链表的长度
                            / 2.当前插入key 与链表当中某个元素的key一致时,当前插入操作可能就是替换了,binCount表示冲突位置(binCount-1)
                            binCount = 1;
                            /迭循环代当前桶位的链表,e是每次循环处理节点
                            for (Node<K,V> e = f;; ++binCount) {
                                /当前循环节点 key
                                K ek;
                                / 条件一:e.hash == hash 成立 表示循环当前的节点和要插入节点的hash值一样,需要进一步判断
                                / 条件二:((ek = e.key) == key || (ek != null && key.equals(ek)))
                                / 成立说明:当前节点和插入节点的key 一致,发生冲突
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    /将当前循环元素赋值给oldVal
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                /当前插入元素 与 循环元素的key 不一致,会走下面
                                /1.更新循环处理节点 为当前节点的下一个节点
                                /2.判断下一个节点是否为null,如果是null,说明当前节点已经是队尾了,插入数据需要追加到队尾节点的后面
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        / 前置条件:该桶位一定不是链表
                        / 条件成立:红黑树代理节点 TreeBin
                        else if (f instanceof TreeBin) {
                            / p 代表红黑树中如果与你插入的节点key 有冲突节点的话,则putTreeVal 方法 会返回冲突节点的引用
                            Node<K,V> p;
                            / 强制设置binCount 为2 ,因为 binCount <= 1时,有其他含义,addCount()中有用
                            binCount = 2;
                            /条件一:p不为null 说明插入节点和红黑树的节点的key有冲突
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                / 将冲突节点的值 赋值给 oldVal
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                / 当前桶位不为null,可能是链表,可能是红黑树
                if (binCount != 0) {
                    /如果binCount >= 8 表示处理的桶位一定是链表
                    if (binCount >= TREEIFY_THRESHOLD)
                        /调用转化链表为红黑树的方法
                        treeifyBin(tab, i);
                    /不等于null 代表与原数据发生冲突,返回原来的值
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        / 统计当前table 一共有多少数据
        / 判断是否到达扩容阈值标准,触发扩容
        addCount(1L, binCount);
        return null;
    }

下一节我们讲addCount方法,再见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值