zoukankan      html  css  js  c++  java
  • 【Java并发集合】ConcurrentHashMap源码解析基于JDK1.8

    concurrentHashMap(基于jdk1.8)

    类注释

    1. 所有的操作都是线程安全的,我们在使用时无需进行加锁。
    2. 多个线程同时进行put、remove等操作时并不会阻塞,可以同时进行,而HashTable在操作时会锁住整个Map。
    3. 在迭代过程中,即使Map及结构被修改,也不会抛出ConcurrentModificationException 。
    4. 除了数组+链表+红黑树的基本结构外,新增了转移节点,是为了保证扩容时的线程安全的节点。
    5. 提供了很多的Stream方法,比如:foreach、search、reduce等。

    类结构

    在这里插入图片描述
    ConcurrentHashMap与HashMap结构几乎相同;都是采用数组+链表+红黑树。
    两者都实现了Map接口,继承了AbstractMap。

    ConcurrentHashMap原理概述

    在ConcurrentHashMap中通过一个Node<K,V>[ ]数组来保存到map中的键值对,而在同一个数组位置时,通过链表和红黑树的形式来保存的。但是这个数组只有在第一次添加元素的时候才会初始化,否则只是初始化一个实例对象,只是设定了sizeCtl变量,这个变量用来判断对象的一些状态和是否需要扩容。

    第一次添加元素的时候,默认初始长度是16,当往map中继续添加元素的时候,通过hash值跟数组长度来决定放在数组的哪个位置,如果出现在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数大于等于8时,并且数组的长度还小于64的话,则会触发扩容机制。如果数组的长度大于等于64,链表的长度大于等于8,就会将这些节点的构成的链表转换成红黑树。

    通过扩容数组的方式将这些节点给分散开。然后将这些元素复制到扩容的新数组中,同一个链表中的元素通过hash值的数组长度位来区分放在 原先的位置 or 放到新数组的相同位置。扩容完成后,如果某个节点是树,同时性质该节点的个数小于等于6,则将由红黑树转换成链表。

    ConcurrentHashMap的属性

        //数组的最大容量 2^30
        private static final int MAXIMUM_CAPACITY = 1 << 30;
    
        /**
         * 数组长度默认是16,必须是2的幂次方
         */
        private static final int DEFAULT_CAPACITY = 16;
    
        /**
         * 数组可能的最大值,需要与toArray()相关方法关联
         */
        static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * 
         * 默认并发度,同时允许多少个线程访问
         */
        private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    
        /**
         * 默认的负载因子时0.75
         * 
         */
        private static final float LOAD_FACTOR = 0.75f;
    
        /**
         * 链表转成红黑树的阈值
         */
        static final int TREEIFY_THRESHOLD = 8;
    
        /**
        * 红黑树转成链表的阈值
         */
        static final int UNTREEIFY_THRESHOLD = 6;
    
        /**
        * 链表转换成红黑树之前还会进行一次判断,只有键值对数量大于64才会发生转换
         */
        static final int MIN_TREEIFY_CAPACITY = 64;
    
        /**
         * Minimum number of rebinnings per transfer step. Ranges are
         * subdivided to allow multiple resizer threads.  This value
         * serves as a lower bound to avoid resizers encountering
         * excessive memory contention.  The value should be at least
         * DEFAULT_CAPACITY.
         */
        private static final int MIN_TRANSFER_STRIDE = 16;
    
        /**
         * The number of bits used for generation stamp in sizeCtl.
         * Must be at least 6 for 32bit arrays.
         */
        private static int RESIZE_STAMP_BITS = 16;
    
        /**
          * 2^15-1,resize时最大的线程数
         */
        private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    
        /**
         * The bit shift for recording size stamp in sizeCtl.
         * sizeCtl记录size大小的偏移量
         RESIZE_STAMP_SHIFT = 32-16=16
         */
        private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    
        /*
         * Encodings for Node hash fields. See above for explanation.
         */
        static final int MOVED     = -1; // 表示正在转移
        static final int TREEBIN   = -2; // 表示已经转换成树,TreeBin节点
        static final int RESERVED  = -3; // hash for transient reservations
        static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
    
        /** 可用的CPU的数量 */
        static final int NCPU = Runtime.getRuntime().availableProcessors();
    
    
    
        /**
         * 桶数组,与HashMap基本一致,延迟加载,不过这里使用了volatile修饰
         */
        transient volatile Node<K,V>[] table;
    
        /**
         * 这个桶数组在扩容的时候使用
         */
        private transient volatile Node<K,V>[] nextTable;
    
        /**
         * 记录元素的个数,也被volatile修饰
         */
        private transient volatile long baseCount;
    
        /**
         * 表初始化和扩容的标志位
         * 默认值是:0
         * 初始化之前:初始化容量大小
         * 正在初始化:-1
         * 扩容前:触发扩容前的元素个数相当于HashMap的threshold,threshold = capacity*loadFactory
         * 正在扩容:-(1+参与扩容的线程数)
         */
        private transient volatile int sizeCtl;
    
        /**
         * 当扩容时需要对元素进行迁移,transferIndex用来作为桶的下标
         */
        private transient volatile int transferIndex;
    
        /**
         * 对CounterCells数组更新操作时,使用到的自旋锁
         */
        private transient volatile int cellsBusy;
    
        /**
         * 计数用,用于计算没来的及更新到baseCount的变化
         */
        private transient volatile CounterCell[] counterCells;
    
        // views
        private transient KeySetView<K,V> keySet;
        private transient ValuesView<K,V> values;
        private transient EntrySetView<K,V> entrySet;
    
    

    重要方法

    JDK1.8以前,ConcurrentHashMap主要使用锁分段的机制保证线程安全,在JDK1.8及其以后,主要使用了CAS+synchronized来实现。CAS是一种无锁并发技术。
    但是ConcurrentHashMap并没有实现自己的CAS,而是会直接使用private static final sun.misc.Unsafe U;
    一下三种方法保证了访问桶数组中的第一个元素是线程安全的。

    //返回节点数组的指定位置的节点  (原子操作)
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
            return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
        }
    //在指定位置设置值(CAS)
        static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                            Node<K,V> c, Node<K,V> v) {
            return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
        }
    //在指定位置设置值(原子操作)
        static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
            U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
        }
    

    ConcurrentHashMap重要的类

    Node<K,V>

    
    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;//key的hash值
            final K key;
            volatile V val;
            volatile Node<K,V> next;//表示链表中的下一个节点
    
            Node(int hash, K key, V val, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.val = val;
                this.next = next;
            }
    
            public final K getKey()       { return key; }
            public final V getValue()     { return val; }
            public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
            public final String toString(){ return key + "=" + val; }
            public final V setValue(V value) {
                throw new UnsupportedOperationException();
            }
    
            public final boolean equals(Object o) {
                Object k, v, u; Map.Entry<?,?> e;
                //如果key和value都不为空并且两个都相等,则返回true
                return ((o instanceof Map.Entry) &&
                        (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                        (v = e.getValue()) != null &&
                        (k == key || k.equals(key)) &&
                        (v == (u = val) || v.equals(u)));
            }
    
            /**
             * Virtualized support for map.get(); overridden in subclasses.
             */
            Node<K,V> find(int h, Object k) {
                Node<K,V> e = this;
                if (k != null) {
                    do {
                        K ek;
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                    } while ((e = e.next) != null);
                }
                return null;
            }
        }
    

    TreeNode节点

    
     static final class TreeNode<K,V> extends Node<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
    
            TreeNode(int hash, K key, V val, Node<K,V> next,
                     TreeNode<K,V> parent) {
                super(hash, key, val, next);
                this.parent = parent;
            }
    
            Node<K,V> find(int h, Object k) {
                return findTreeNode(h, k, null);
            }
    
        
    
    

    TreeBin用作树的头结点字存储root和first节点不存储key和value

    
     static final class TreeBin<K,V> extends Node<K,V> {
            TreeNode<K,V> root;
            volatile TreeNode<K,V> first;
            volatile Thread waiter;
            volatile int lockState;
            // 锁的状态值
            static final int WRITER = 1; // set while holding write lock
            static final int WAITER = 2; // set when waiting for write lock
            static final int READER = 4; // increment value for setting read lock
    
         
    
    

    ForwardingNode

    
     static final class ForwardingNode<K,V> extends Node<K,V> {
            final Node<K,V>[] nextTable;
            ForwardingNode(Node<K,V>[] tab) {
                super(MOVED, null, null, null);
                this.nextTable = tab;
            }
    

    构造函数

        /**
         * 空参构造函数,默认table的size是16.
         */
        public ConcurrentHashMap() {
        }
    
        /**
         * 
         *创建一个新的map,可以通过参数指定table的size。
         * @param initialCapacity The implementation performs internal
         * sizing to accommodate this many elements.
         * @throws IllegalArgumentException 如果参数是负数就会抛出IllegalArgumentException
         */
        public ConcurrentHashMap(int initialCapacity) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException();
                //判断参数是不是大于所限制的最大值,如果大于最大值就设置成最大值,反之调用tableSizeFor方法
                //设置初始table容量
            int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
            //初始化sizeCtl
            this.sizeCtl = cap;
        }
    
        /**
         * 创建与指定map映射的map
         *
         * @param m the map
         */
        public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
            this.sizeCtl = DEFAULT_CAPACITY;
            putAll(m);
        }
    
        /**
         *
         * @param initialCapacity the initial capacity. The implementation
         * performs internal sizing to accommodate this many elements,
         * given the specified load factor.
         * @param loadFactor the load factor (table density) for
         * establishing the initial table size
         * @throws IllegalArgumentException if the initial capacity of
         * elements is negative or the load factor is nonpositive
         *
         * @since 1.6
         */
        public ConcurrentHashMap(int initialCapacity, float loadFactor) {
            this(initialCapacity, loadFactor, 1);
        }
    
        /**
         *
         * @param initialCapacity 初始容量
         * @param loadFactor 负载因子当容量达到 initialCapacty*loadFactory时,执行扩容
         * @param concurrencyLevel 并发更新的线程数
         * @throws IllegalArgumentException if the initial capacity is
         * negative or the load factor or concurrencyLevel are
         * nonpositive
         */
        public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
            if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
                throw new IllegalArgumentException();
            if (initialCapacity < concurrencyLevel)   // Use at least as many bins
                initialCapacity = concurrencyLevel;   // as estimated threads
            long size = (long)(1.0 + (long)initialCapacity / loadFactor);
            int cap = (size >= (long)MAXIMUM_CAPACITY) ?
                MAXIMUM_CAPACITY : tableSizeFor((int)size);
            this.sizeCtl = cap;
        }
    
    
    

    在任何构造方法中都没有直接对存储元素的Node类型的table进行初始化,而是在第一次put的时候进行初始化,这叫做延迟加载。

    这里具体看一下初始化方法initTable

    private final Node<K,V>[] initTable() {
            Node<K,V>[] tab; int sc;
            while ((tab = table) == null || tab.length == 0) {
                if ((sc = sizeCtl) < 0)
                //如果正在扩容或者正在进行初始化那就释放CPU执行权
                    Thread.yield(); // lost initialization race; just spin
                else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                //将sizeCtl设置成-1,告诉其他线程正在进行初始化
                    try {
                        if ((tab = table) == null || tab.length == 0) {
                        //如果sizeCtl>0,那就初始化大小为sizeCtl的数组,如果=0,就初始化为16的默认大小
                            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);//相当于 sc = n*0.75
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                    break;
                }
            }
            return tab;
        }
    

    put方法

       /**
       * 单纯的调用putVal方法,第三个参数设置成了false
       * 当设置为false的时候表示这个value一定会被设置
       * 如果设置成true,表示只有这个key的value是null的时候才会被设置
       **/
       public V put(K key, V value) {
            return putVal(key, value, false);
        }
    
    

    putVal

    实现的思路

    • 如果数组为空,初始化数组
    • 计算当前桶的位置有没有值,没有的话,CAS创建,失败继续自旋,直到成功添加
    • 如果该节点是转移节点(正在扩容,)就会一直自旋等待扩容完成后在进行自增
    • 如果这个位置不为null,先锁定该槽点,保证其他线程不能进行操作,如果是链表,新增到链表的尾部,如果是红黑树使用红黑树的新增方法进行新增;
    • 新增完成后,check需不需要进行扩容,需要的话进行扩容。
        /** Implementation for put and putIfAbsent */
        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;
                //如果tab 是空进行tab初始化
                if (tab == null || (n = tab.length) == 0)
                    tab = initTable();
                    //如果索引的位置没有值是null那就直接创建
                else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                   //在索引是i的位置上创建新的元素,当i位置时空时,表示创建成功没结束for循环,否则继续自旋
                   if (casTabAt(tab, i, null,
                                 new Node<K,V>(hash, key, value, null)))
                        break;                   // no lock when adding to empty bin
                }
                //如果当前节点是转移节点(MOVED=-1),表示正在进行扩容,则会等待扩容完成
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);
                else {
                //如果该索引的位置不是null,已经有值
                    V oldVal = null;
                    //锁住当前节点,保证其他线程不进行更改
                    synchronized (f) {
                    //链表
                        if (tabAt(tab, i) == f) {
                            if (fh >= 0) {
                            //binCount被赋值,代表有线程更新表的过程
                                binCount = 1;
                                //遍历链表
                                for (Node<K,V> e = f;; ++binCount) {
                                    K ek;
                                    //如果key的hash和链表中的某个节点的key的hash相同
                                    //并且值页相同,然后将旧的值赋值给oldVal
                                    if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                         (ek != null && key.equals(ek)))) {
                                        oldVal = e.val;
                                        //如果onlyIfAbsent=false代表可以设置,否则不能更新值
                                        if (!onlyIfAbsent)
                                            e.val = value;
                                        //更新值以后退出
                                        break;
                                    }
                                    //如果遍历过后没有找到对应的key那就将新的node放在链表的最后
                                    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) {
                                Node<K,V> p;
                                binCount = 2;
                                //在putTreeVal方法中,再给红黑树着色旋转的时候,会锁住树的根节点
                                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                               value)) != null) {
                                    oldVal = p.val;
                                    if (!onlyIfAbsent)
                                        p.val = value;
                                }
                            }
                        }
                    }
                    //binCount不为空,并且oldVal有值的情况下,说明新增成功
                    if (binCount != 0) {
                        //binCount>=8需要转成红黑树
                        if (binCount >= TREEIFY_THRESHOLD)
                            treeifyBin(tab, i);
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            //检查一下是否需要进行扩容,如果需要进行扩容,调用transfer进行扩容
            addCount(1L, binCount);
            return null;
        }
    

    扩容

    扩容方法通过下面的方法来发起

    • addCount方法:当容器中的元素发生改变时会进行调用,主要是检查容器当前的状态,判断是否扩容。
    • tryPresize方法:在treeIf斌和putAll方法中调用,treeIfbin主要是在put元素后,判断该链表的长度是不是大于8,如果超过则会调用这个方法来扩容数组或者把链表转化为树。
    • helpTransfer方法:是在当一个线程要对表中元素进行操作时候,如果检测到节点的HASH值为MOVED的时候,就会调用help Transfer方法,helpTransfer方法又会调用transfer方法进行扩容

    扩容的具体实现还是通过transfer方法完成

    将数组中的节点复制到新的数组中的相同位置,或者移动到拷贝部分的相同位置。
    拷贝数组时,先把原先数组的节点锁住,保证原数组的节点不能操作,成功拷贝到新数组时,把原数组的当前节点赋值称为转移节点,这时如果有线程在put这个节点位置时,发现是转移节点就会一直等待,所以在扩容完成之前这个节点对应的值是不发生变化的。扩容分为两步,第一步是新建空数组,第二是移动或拷贝每个元素到新数组中去

    
     private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
            //n代表原先数组的长度,移动过程中会将桶分段,stride代表每段的长度最小值是16
            int n = tab.length, stride;
            if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
                stride = MIN_TRANSFER_STRIDE; // subdivide range
            if (nextTab == null) {            // 初始化新数组
                try {
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                    nextTab = nt;
                } catch (Throwable ex) {      // try to cope with OOME
                    sizeCtl = Integer.MAX_VALUE;
                    return;
                }
                nextTable = nextTab;
                transferIndex = n; //transferIndex用于记录当前迁移的进度,需要注意的是迁移元素从最后一个节点开始
            }
            //新数组的长度
            int nextn = nextTab.length;
            //fwd代表转移节点,如果原数组上是转移节点,说明该节点正在被扩容
            ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
           //advance=true代表这次循环已经完成,可以开始下一个循环
           boolean advance = true;
           //finishing表示当前线程数组迁移是否完成
            boolean finishing = false; // to ensure sweep before committing nextTab
          //死循环 自旋操作
          for (int i = 0, bound = 0;;) {
                Node<K,V> f; int fh;
                while (advance) {
                    int nextIndex, nextBound;
                    if (--i >= bound || finishing)//结束本次循环
                        advance = false;
                    else if ((nextIndex = transferIndex) <= 0) {//已经拷贝完成
                        i = -1;
                        advance = false;
                    }
                    else if (U.compareAndSwapInt
                             (this, TRANSFERINDEX, nextIndex,
                              nextBound = (nextIndex > stride ?
                                           nextIndex - stride : 0))) {
                      //为当前线程分配桶的区间,当前线程需要将负责这个区间内的桶元素移动到nextTable 中
                      bound = nextBound;
                        i = nextIndex - 1;
                        advance = false;
                    }
                }
                //判断当前线程是否完成所有的节点的迁移
                if (i < 0 || i >= n || i + n >= nextn) {
                    int sc;
                    if (finishing) {//finishing=true代表完成拷贝过程,那就把新数组赋值给table
                        nextTable = null;
                        table = nextTab;
                        sizeCtl = (n << 1) - (n >>> 1);//相当于 n * 0.75
                        return;
                    }
                    //将参与扩容的线程数量减一
                    if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                        if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                            return;
                        finishing = advance = true;
                        i = n; // 退出的线程需要再检查一遍容器的状态
                    }
                }
                else if ((f = tabAt(tab, i)) == null)
                    advance = casTabAt(tab, i, null, fwd);
                else if ((fh = f.hash) == MOVED)
                    advance = true; // 当前桶节点已经被处理
                else {
                //开始进行桶节点的拷贝
                    synchronized (f) {
                        if (tabAt(tab, i) == f) {
                            Node<K,V> ln, hn;
                            if (fh >= 0) {
                                int runBit = fh & n;//n的值是原先数组的长度
                                Node<K,V> lastRun = f;
                                for (Node<K,V> p = f.next; p != null; p = p.next) {
                                    int b = p.hash & n;
                                    if (b != runBit) {
                                        runBit = b;
                                        lastRun = p;
                                    }
                                }
                                if (runBit == 0) {
                                    ln = lastRun;
                                    hn = null;
                                }
                                else {
                                    hn = lastRun;
                                    ln = null;
                                }
                                //如果节点只有单个数据直接拷贝;如果是链表进行链表拷贝
                                for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                    int ph = p.hash; K pk = p.key; V pv = p.val;
                                    if ((ph & n) == 0)
                                        ln = new Node<K,V>(ph, pk, pv, ln);
                                    else
                                        hn = new Node<K,V>(ph, pk, pv, hn);
                                }
                                //在新数组的位置上设置值
                                setTabAt(nextTab, i, ln);
                                setTabAt(nextTab, i + n, hn);
                                setTabAt(tab, i, fwd);
                                advance = true;
                            }
                            else if (f instanceof TreeBin) {//红黑树的拷贝
                                TreeBin<K,V> t = (TreeBin<K,V>)f;
                                TreeNode<K,V> lo = null, loTail = null;
                                TreeNode<K,V> hi = null, hiTail = null;
                                int lc = 0, hc = 0;
                                for (Node<K,V> e = t.first; e != null; e = e.next) {
                                    int h = e.hash;
                                    TreeNode<K,V> p = new TreeNode<K,V>
                                        (h, e.key, e.val, null, null);
                                    if ((h & n) == 0) {
                                        if ((p.prev = loTail) == null)
                                            lo = p;
                                        else
                                            loTail.next = p;
                                        loTail = p;
                                        ++lc;
                                    }
                                    else {
                                        if ((p.prev = hiTail) == null)
                                            hi = p;
                                        else
                                            hiTail.next = p;
                                        hiTail = p;
                                        ++hc;
                                    }
                                }
                                //这里会判断要不要变回链表 UNTREEIFY_THRESHOLD=6
                                ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                    (hc != 0) ? new TreeBin<K,V>(lo) : t;
                                hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                    (lc != 0) ? new TreeBin<K,V>(hi) : t;
                                setTabAt(nextTab, i, ln);
                                setTabAt(nextTab, i + n, hn);
                                setTabAt(tab, i, fwd);
                                advance = true;
                            }
                        }
                    }
                }
            }
        }
    
    
    

    扩容的关键点

    1. 拷贝桶节点时,会把原数组的节点锁住;
    2. 拷贝成功后,会把原数组的节点设置成转移节点,这样如果有数据需要put到该节点时,发现该节点时转移节点会进行等待,知道扩容成功后,才能继续执行。
    3. 从尾到头进行拷贝,但拷贝后并不是原先数组的绝对倒序
    4. 等扩容完成后将新数组的值赋值给数组容器,之前等待put的线程才能继续put。

    get

    /*需要注意的是get方法的参数 key不允许传入null,否则会抛出NullPointerException*/
    public V get(Object key) {
            Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
            //计算hashcode
            int h = spread(key.hashCode());
           //不是空数组并且当前索引下标的节点不是空
           if ((tab = table) != null && (n = tab.length) > 0 &&
                (e = tabAt(tab, (n - 1) & h)) != null) {
                if ((eh = e.hash) == h) {
                    if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    //如果第一个几点的键与要找的key相等,就直接返回对应的value
                        return e.val;
                }
                else if (eh < 0)//如果是红黑树或者是转移节点使用对应的find方法
                    return (p = e.find(h, key)) != null ? p.val : null;
                //如果是链表就开始遍历
                while ((e = e.next) != null) {
                //找到对应的key返回相应的value
                    if (e.hash == h &&
                        ((ek = e.key) == key || (ek != null && key.equals(ek))))
                        return e.val;
                }
            }
            //tab是null就返回null
            return null;
        }
    
    

    参考
    https://www.cnblogs.com/zerotomax/p/8687425.html#go0
    https://www.rayjun.cn/
    https://www.imooc.com/read/47/article/858

  • 相关阅读:
    PCB封装分配
    1.26-CAD异形封装的制作
    1.40-CAE封装6个实例单门制作
    简单的URL解析
    冒泡排序和简单的递归
    作用域的提升
    数组的去重,以及随机一个验证码
    数组和字符串的相互转换。
    substring,substr,和slice的区别详解。
    去除Input输入框中边框的方式
  • 原文地址:https://www.cnblogs.com/itjiangpo/p/14181378.html
Copyright © 2011-2022 走看看