ConcurrentHashMap的put(k, v)方法源码分解

7.1——putVal()方法源码分解

    public V put(K key, V value) {
                //onlyIfAbsent:false 有相应的key和value替换 true有相应的key和value就不能成功写入
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //控制k 和 v 不能为null
        if (key == null || value == null) throw new NullPointerException();

        //通过spread方法,可以让高位也能参与进寻址运算。
        int hash = spread(key.hashCode());
        //binCount表示当前k-v 封装成node后插入到指定桶位后,在桶位中的所属链表的下标位置
        //0 表示当前桶位为null,node可以直接放着
        //2 表示当前桶位已经可能是红黑树
        int binCount = 0;

        //tab 引用map对象的table
        //自旋
        for (Node<K,V>[] tab = table;;) {
            //f 表示桶位的头结点
            //n 表示散列表数组的长度
            //i 表示key通过寻址计算后,得到的桶位下标
            //fh 表示桶位头结点的hash值
            Node<K,V> f; int n, i, fh;

            //CASE1:成立,表示当前map中的table尚未初始化..
            if (tab == null || (n = tab.length) == 0)
                //最终当前线程都会获取到最新的map.table引用。
                tab = initTable();
            //CASE2:i 表示key使用路由寻址算法得到 key对应 table数组的下标位置,tabAt 获取指定桶位的头结点 f
            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操作失败,表示在当前线程之前,有其它线程先你一步向指定i桶位设置值了。
                //当前线程只能再次自旋,去走其它逻辑。
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }

            //CASE3:前置条件,桶位的头结点一定不是null。
            //条件成立表示当前桶位的头结点 为 FWD结点,表示目前map正处于扩容过程中..
            else if ((fh = f.hash) == MOVED)
                //看到fwd节点后,当前节点有义务帮助当前map对象完成迁移数据的工作
                //学完扩容后再来看。
                tab = helpTransfer(tab, f);

            //CASE4:当前桶位 可能是 链表 也可能是 红黑树代理结点TreeBin
            else {
                //当插入key存在时,会将旧值赋值给oldVal,返回给put方法调用处..
                V oldVal = null;

                //使用sync 加锁“头节点”,理论上是“头结点”
                synchronized (f) {
                    //为什么又要对比一下,看看当前桶位的头节点 是否为 之前获取的头结点?
                    //为了避免其它线程将该桶位的头结点修改掉,导致当前线程从sync 加锁 就有问题了。之后所有操作都不用在做了。
                    if (tabAt(tab, i) == f) {//条件成立,说明咱们 加锁 的对象没有问题,可以进来造了!

                        //条件成立,说明当前桶位就是普通链表桶位。
                        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值与插入节点的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 时有其它含义,所以这里设置为了2 回头讲 addCount。
                            binCount = 2;

                            //条件一:成立,说明当前插入节点的key与红黑树中的某个节点的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);
                    //说明当前线程插入的数据key,与原有k-v发生冲突,需要将原数据v返回给调用者。
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }

        //1.统计当前table一共有多少数据
        //2.判断是否达到扩容阈值标准,触发扩容。
        //binCount:put》=1 桶位链表长度 =1,表示插入数据和桶位数据的key一致 也可能是也链表的每个Key一致 进行替换
        //          =0  插入的桶位 为null
        //          =2  桶位的数据为红黑树

        //romove -1L binCount:-1 :删除 不用判断是不是扩容
        addCount(1L, binCount);

        return null;
    }b

7.2——initTable()方法源码分解

map中的table尚未初始化 方法详解

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

            if ((sc = sizeCtl) < 0)
                //大概率就是-1,表示其它线程正在进行创建table的过程,当前线程没有竞争到初始化table的锁。
                Thread.yield(); // lost initialization race; just spin

            //1.sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
            //2.sizeCtl>0如果table未初始化,表示初始化大小
            //3.sizeCtl>0如果table已经初始化,表示下次扩容时的 触发条件(阈值)
                                 // -1 是一把锁 那个线程cas成功就可以进去
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    //这里为什么又要判断呢? 防止其它线程已经初始化完毕了,然后当前线程再次初始化..导致丢失数据。
                    //条件成立,说明其它线程都没有进入过这个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 >>> 2  => 等于 1/4 n     n - (1/4)n = 3/4 n => 0.75 * n
                        //sc 0.75 n 表示下一次扩容时的触发条件。
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //1.如果当前线程是第一次创建map.table的线程话,sc表示的是 下一次扩容的阈值
                    //2.表示当前线程 并不是第一次创建map.table的线程,当前线程进入到else if 块 时,将
                    //sizeCtl 设置为了-1 ,那么这时需要将其修改为 进入时的值。
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    } 	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值