ConcurrentHashMap源码阅读笔记

继承关系

ConcurrentHashMap源码阅读笔记_第1张图片
Map和HashMap大家应该都比较熟络了,这里就看继续的各个类了。

ConcurrentHashMap知识点概述

以下内容来自ConcurrentHashMap的注释。JDK版本1.8

  • 线程安全的哈希表,有对应版本的HashTable的每一个方法。
  • 检索操作不加锁,所以可能和更新操作有重叠,返回的值可能是刚更新的值(换句话是你调用get的时候可能哈希表里面可能没有对应的key,但是由于多线程的原因,你可能返回其他线程在你调用get后插入的值)。
  • 不会抛出ConcurrentModificationException异常.
  • iterators在同一时刻只能被一个线程使用。
  • 包括size、isEmpty和containsValue在内的聚合状态方法只有在其他线程未进行更新操作时才有用,这些瞬时状态只能用于监控或预估,不适合做程序控制。
  • 负载因子0.75,如果可以预估容量,最好在初始化时指定初始化容量。
  • 尽量避免hash碰撞。
  • 不允许null作为键或值
  • 哈希表的初始化时发生在第一次插入时,容量是2的次方大小。容量采用2的次方是为了方便扩容。
  • 红黑树的检索时间复杂度是O(log N),所以HashMap的检索时间复杂度是O(1)~O(log N).

核心常量

//哈希表的最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//哈希表的默认初始容量(必须是2的次方数)
private static final int DEFAULT_CAPACITY = 16;
//数组的最大容量(不需要是2的次方数)
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//负载因子,默认0.75,当哈希表元素个数达到当前容量的0.75时发生扩容。
private static final float LOAD_FACTOR = 0.75f;
//当bin下面的链表长度达到这个值时,这个bin下面的节点转换成TreeNode
static final int TREEIFY_THRESHOLD = 8;
//移除操作时,在bin下的节点从TreeNode转成Node的临界值。
static final int UNTREEIFY_THRESHOLD = 6;
//将bin下的节点树化时哈希表大小的最小值。哈希表大小超过该值才树化bin,否则进行扩容。
static final int MIN_TREEIFY_CAPACITY = 64;
static final int MOVED     = -1; // Forwarding节点hash值
static final int TREEBIN   = -2; //tree的根节点hash值
static final int RESERVED  = -3; // 临时保留节点的hash值
static final int HASH_BITS = 0x7fffffff; // 正常节点哈希的可用位
//可用的CPU个数
static final int NCPU = Runtime.getRuntime().availableProcessors();
//基本计数器值,主要在没有争用时使用,但也可作为表初始化竞争期间的后备。 通过 CAS 更新。
private transient volatile long baseCount;
/**
表初始化和调整大小控制。
如果为负,则表正在初始化或调整大小:
	-1 表示初始化,
	否则 -(1 + 活动调整大小线程的数量)。 
非负,当 table 为 null 时,保存创建时使用的初始表大小,或默认为 0。 初始化后,保存下一个要调整表格大小的元素计数值。
*/
private transient volatile int sizeCtl;
//调整大小和/或创建 CounterCell 时使用的自旋锁(通过 CAS 锁定)。
private transient volatile int cellsBusy;
//计数器单元格表。 当非空时,大小是 2 的幂。
private transient volatile CounterCell[] counterCells;

关键内部类

Node

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
	volatile V val;
    volatile Node<K,V> next;
 // other codes
}

可以看到Node是对Map.Entry在这里插入代码片的一个实现,除了hash、k、v外还有一个指向下一个节点的引用。

Segment

    /**
     * Stripped-down version of helper class used in previous version,
     * declared for the sake of serialization compatibility
     */
    static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }

从注释可以看到,Segment在这里其实没啥用,只是一个对1.7的兼容。

  • jdk1.7的ConcurrentHashMap是根据Segment + HashEntry + Unsafe实现的。
  • jkd1.8的ConcurrentHashMap是根据Synchronized + CAS + Node + Unsafe实现的。

ForwardingNode

    /**
     * A node inserted at head of bins during transfer operations.
     */
    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;
        }
        //Other codes
   	}

ForwardingNode继承于Node,在resizing的时候,用于放在bin头用于标识的类。

CounterCell

 @sun.misc.Contended 
static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

用于分布计数的填充单元格。在hash表元素个数计数的时候会用到。

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;
        //other codes
  }

树化节点,继承于Node,有指向父级,左右,和下一个节点的引用。

通过red属性其实就可以猜到concurrentHashMap里面用的是红黑树,其实HashMap用的也是红黑树。

TreeBin

  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;
        // values for 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
        //OTHER cods
     /**
       * Acquires write lock for tree restructuring.
       */
      private final void lockRoot() {
          if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
              contendedLock(); // offload to separate method
      }
}

从注释上了解到,bin本身不持有k和v,他持有一个指向TreeNode列表和树的根的引用,还维护了一把读写锁来强迫写操作在重组树操作前必须等读操作完成才能进行。
其加锁操作是一来就用CAS去加锁,如果加锁失败就进入抢锁环节:

private final void contendedLock() {
   boolean waiting = false;
   for (int s;;) {
       if (((s = lockState) & ~WAITER) == 0) {
           if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
               if (waiting)
                   waiter = null;
               return;
           }
       }
       else if ((s & WAITER) == 0) {
           if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
               waiting = true;
               waiter = Thread.currentThread();
           }
       }
       else if (waiting)
           LockSupport.park(this);
   }
}

如果有其他写线程在进行,看有没有人等待,如果没人等待,则把锁状态改为等待状态,并把等待线程写成自己,然后把这个节点park起来。如果这个时候已经有人等待了,那你就一直循环等着变成waiter。等到读线程完成后再来unpark。

LockSupport的park和unpark没有太深究,简单看了下,大意应该是,A线程调用park后,就相当于在说他需要一个“许可”来继续执行,在没有得到许可之前就挂起。然后B线程执行unpark后,A线程就得到了许可,A线程会马上继续运行。

put源码解析

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        //将key散列hash得到一个hash值
        int hash = spread(key.hashCode());
        //记录
        int binCount = 0;
        //将当前的hash表数组赋予tab,进入死循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //如果tab没有初始化,初始化哈希表
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //通过hash值拿到哈希表数组中的一个位置上的节点f,如果f是空的则创建一个新节点,然后用CAS操作把新借点插入到i位置。
            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
            }
            //如果f的hash值是MOVED,当前线程则去协助扩容。
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            //上面的if都没中,说明该位置已经存了一个节点了,开始根据当前节点是链表还是树节点来执行对应的插入操作
            else {
                V oldVal = null;
                //先用synchronized来把数组里面的节点f锁起来
                synchronized (f) {
                	/**
                	再次确认tab的i位置还是不是节点f,因为在加锁之前可能别的线程已经对tab进行了其他更新操作,使得f已经不在tab的i位置了。
                	这个时候就该释放锁f,再次乖乖的循环。
                	*/
                    if (tabAt(tab, i) == f) {
                   		 //查看f节点的hash值fh是不是>=0,大于0说明这个bin下面的节点是链表
                        if (fh >= 0) {
                        	//binCount初始化为1
                            binCount = 1;
                            //逐个变量链表,同时++binCount。
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                               	/**判断链表中的节点的key和传入的key是否能匹配上(hash相等,equals()为ture)
                               	,匹配则直接用新value替换旧value,然后直接退出死循环。
                               	【注意】此时binCount可能并没有记录到链表中实际的节点树,所以猜测后续操作如果binCount没有增加不会对结果又影响
                                */
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //pred记录上一节点
                                Node<K,V> pred = e;
                                /**
                                如果后面没有更多节点了,那么构建新节点放到链表尾部,同时退出死循环,
                                【注意】此时binCount已经记录好了这个bin下面的节点总数,包括新插入的binCount
                                */
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //节点的hash值小于0,看f是不是一个TreeBin(记不得TreeBin是什么了就回到关键内部类再看看)
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;//初始binCount为2,跟等会儿的addCount的第二个参数有关系。
                            //用TreeBin的方式插入新的hash和value
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                /**
                对binCount做校验,只有synchronized(f)后,校验发现f已经不在i位置了才会在binCount==0的情况下执行到这里,此时应该什么都不做,让其进入下次循环。
                */
                if (binCount != 0) {
                	/**如果binCount超过TREEIFY_THRESHOLD就需要对节点做树化操作,这里就解释了为什么链表里面直接替换,不需要统计完实际的binCount并无影响(当然是因为对addCount也没影响)*/
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    //如果有旧值,直接return,就不用做后面的addCount操作了,因为本来就没有add
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //addCount:就是把哈希表的节点格式+1操作
        addCount(1L, binCount);
        return null;
    }

流程图表示

这里代码逻辑比较复杂,画个流程图帮助分析。图中三个黄色块的逻辑看下文继续分析,这仍然是ConcurrentHashMap的核心。
ConcurrentHashMap源码阅读笔记_第2张图片

哈希表初始化

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        //一个while循环,退出条件是hash表被初始化
        while ((tab = table) == null || tab.length == 0) {
        	//对sizeCtl做判断,看是不是小于0,小于0表示哈希表正在初始化或扩容,可以回去看看核心常量里面。
        	//如果已经有其他线程在初始化或者扩容了,当前线程yield(放弃CPU,意思就是,既然有人在初始化了,那我就不干了,你们慢慢搞)
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
             //没有线程在初始化,CAS对设置sizeCtl
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
	                //再次确认哈希表没有被初始化
	                //ps:难道其他线程初始化哈希表后,sizeCtl值没变?好像有可能。这个有点绕,在这里先不深究。
                    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;
                        //下次扩容的元素个数,即(3/4)n。当元素个数达到容量的0.75时触发扩容操作
                        sc = n - (n >>> 2);
                    }
                } finally {
                	//将sc写回给sizeCtl
                	/**
                		ps:这里追究下刚才那个再次验证哈希表是否初始化的问题。
                		倘若线程A的初始化容量是N,线程B的初始化容量是M,线程A先拿到sizeCtl为M,接着cpu时间片耗完,线程B来了,看到哈希表还没有初始化,则对哈希表进行初始化
                		,然后N*0.75恰好等于M,这个时候A线程得到CPU,进行CAS操作的时候发现现在的sizeCtlh和刚才看到的sc刚好一样
                		,那么久加锁成功。但其实这个时候哈希表已经被线程B初始化完成了。
                		【综上属于个人推测,若有问题请指出】
                	*/
                    sizeCtl = sc;
                }
                break;
            }
        }
        //完成初始化
        return tab;
    }

节点数+1

addCount(1L, binCount);
/**
这里开始之前先回顾下,concurrentHashMap计数是BaseCount+CounterCell[]来共同完成计数的
*/
private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    /**
    如果counterCells不是空的,自接进入if,
    如果counterCells是空的,那就直接到BaseCount上去加x,如果加x失败了,就进if
    */
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        /**
        	1.如果as是空的,进fullAddCount;
        	2.如果as的长度为0,进fullAddCount
        	3.as中去找一个位置,如果这个位置是null,进fullAddCount
        	4.如果上面的都是false,说明在counterCells找到一个位置可以累加x了,cas操作在这个位置上累加x。如果cas失败了,那还是fullAddCount吧。
        	【fullAddCount的源码看后面】
        */
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            /**
            fullAddCount结束后直接return了?难道是觉得这个线程去fullAddCount过后已经干太多活了,为了提高响应速度,让他直接返回了???
            */
            return;
        }
        /**
        	如果check<=1,直接结束;
        	结合上面的put操作,到这里check应该是大于1了,继续下面的操作
        */
        if (check <= 1)
            return;
         /**统计现在元素总和s,统计方法就是把counterCells里面的值和BaseCount全部加起来
         【这个地方没有加锁,后面注意下不加锁的s是不是没影响】
         */
        s = sumCount();
    }
    //上面操作完成了对k的追加,put之后到这里check肯定是大于0的,暂不考虑下小于0的情况
    if (check >= 0) {
    
        Node<K,V>[] tab, nt; int n, sc;
        /**
        	一个while循环,循环条件是:
        	1.s达到扩容值;
        	2.哈希表不是null(暂未深究为什么哈希表会是null)
        	3.哈希表的数组没有达到最大上限;
        	换句话说就是,如果没到扩容值或者已经到了最大上限了,那么就不会进行下面的扩容操作。
        	【这个地方貌似对s读并不要求就是你这个线程+1后的s,这样就可以谁发现容量满了谁就来扩容】
        */
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
               /**
               看下resizeStamp方法的注解:
               		返回用于调整大小为 n 的表的标记位。 向左移动 RESIZE_STAMP_SHIFT 时必须为负。
               */
            int rs = resizeStamp(n);
            /**
            sc小于零,太久了再看看sc小于0是什么意思:
				-1 表示初始化,
				否则 -(1 + 活动调整大小线程的数量)。 
			小于0就表示正在初始化或者调整大小,不管哪个,该线程都会去帮忙调整。一堆位运算有点烧脑,后面再来深究,调整的方法都是transfer。后面看看transfer方法。
            */
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

流程图

ConcurrentHashMap源码阅读笔记_第3张图片

fullAddCount

fullAddCount直译过来是“完整添加计数”的意思,在尝试往counterCells里面已有的Cell里面追加x失败的情况下就把追加x的任务交给这个方法了。

private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        //为当前线程构建一个h
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;
            /**
           		如果counterCells非空,且长度n大于0
            */
            if ((as = counterCells) != null && (n = as.length) > 0) {
            	/**
            	将(n-1)按位与上h得到一个下标,取出as中对应的cell对象a。
            	【假设n=4,h=9,那么n-1的二进制就是0(好几个)011,h的二进制是0(好几个)1001,按位与起来就是0(好几个)011,等于3.
            	所以(n - 1) & h是一个小于n的整数】
            	*/
                if ((a = as[(n - 1) & h]) == null) {
                /**
					如果当前位置的a是null,看cellsBusy是不是0
					【cellsBusy默认是0,核心常量里面有提到,cellsBusy是计数的时候的一把自旋锁,0表示没有锁】
                */
                    if (cellsBusy == 0) {            // Try to attach new Cell
                    	//新建一个cell准备把这个cell插入到a的位置
                        CounterCell r = new CounterCell(x); // Optimistic create
                        //如若Cell还是没上锁的状态,自旋上锁
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            try {               // Recheck under lock
                                CounterCell[] rs; int m, j;
                                //上锁成功后再来一次刚才的操作,找到一个下标j对应的空位置
                                //如果找到了就把新的cell放在该位置,created标识记为true,如果失败了就释放锁cellsBusy 
                                if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            //新的cell设置成功就退出死循环完成x的追加
                            if (created)
                                break;
                            //如果刚才的新cell没有设置成功,就继续循环
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                /**
                	这个地方的if else嵌套比较深,不是很好看代码,我们写代码应该尽量避免。回顾下,这个else if是在刚才取cell取出来不是null的情况下进行,这个地方对wasUncontended进行判断,如果是false则将其置位ture,注释上说的是CAS已知是失败的,然后刷新后继续。
                */
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //尝试在刚才取出的cell上面通过CAS进行追加x,如果追加成功则退出死循环,
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                //看下counterCells还是不是刚才的as,或者长度n是不是已经到达可用Cpu数量了,如果任意一个满足,这是collide 为fasle
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale
                /**
                到这里的情况应该就是,cell非空,并且counterCells 也没变,长度也没达到最大限制,
                这个时候就把collide 设置为true,表示发生了碰撞,需要对counterCells扩容了。
                */
                else if (!collide)
                    collide = true;
                  /**
                  看cell仍然没有处于busy状态(也就是其他线程正在对其扩容),那就CAS对其加锁。
                  */
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                /**
	                如果加锁成功,对counterCells 再次确认是不是没有被该表,防止在加锁成功前被别的线程扩容过了。
                */
                    try {
                        if (counterCells == as) {// Expand table unless stale
                        /**
                        	新建一个容量为刚才那个CounterCell两倍的数组rs。
                        	并把刚才的cell一个个搬过来(搬的是cell的引用,并不用担心在搬的过程中别的线程去追加会丢失),然后用这个rs替换刚才的数组,最后释放cellsBusy 锁把collide置位false。
                        	【注意这个过程只是对CounterCell扩容,并没有去追加x,其实我觉得为什么不在这个过程就直接把x追加上去了呢
                        	,先前长度为n,那我扩容之后的第n+1个肯定是空的,我直接再搬完之后
                        	,把counterCells 指向rs之前在第n+1个位置上新建节点追加x,然后释放锁后,该线程就可以退出死循环了啊(个人看法,有没考虑到的地方还请指出)】
                        */
                            CounterCell[] rs = new CounterCell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            counterCells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                //上面一系列操作都没有在counterCells上追加成功x,重新生成h继续循环
                h = ThreadLocalRandom.advanceProbe(h);
            }
            /**
            	回顾下执行到这里的条件:counterCells为null或长度为0
            	如果counterCells没有被上锁,并且counterCells没有被改变,那就尝试对其上锁,然后开始初始化counterCells
            */
            else if (cellsBusy == 0 && counterCells == as &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {                           // Initialize table
                	/**
                	再次确认counterCells没有改变后,初始化一个长度为2的数组,然后在h&1的位置上新建一个Cell来放x
                	,最后把counterCells指向初始化的数组,释放锁,然后退出循环。
                	【这里其实没太懂为什么要try,难道会有什么异常?】
                	
                	*/
                    if (counterCells == as) {
                        CounterCell[] rs = new CounterCell[2];
                        rs[h & 1] = new CounterCell(x);
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //如果上门一系列操作都没有追加上x,那就再次尝试在Basecout上追加x,如果成功了就直接退出循环,否则继续循环,知道成功为止
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          // Fall back on using base
        }
    }

流程图

整个流程比较复杂,有画错的地方还望指出哈
ConcurrentHashMap源码阅读笔记_第4张图片

transfer和helpTransfer

从上面的分析来看,在put是发现节点是MOVED时会helpTransfer,在节点数+1后发现达到扩容条件后会transfer。
接下来看看ConcurrentHashMap是怎么扩容与协助扩容的吧。

transfer

    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        /**
        第一步 分任务:
        	1.如果当前可以线程数大于1:
        		n(即当前的哈希表数组长度)右移3位(即除以8),然后再除以当前可用cpu数;
        	2.如果当前可用线程数是1,那么任务步长是n(因为可以cpu只有一个,同时也就只有一个线程);
        	3.如果上面分下来的任务数小于最小步长,那么任务步长就等于最小步长(默认16)。	
        */
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        /**
        	如果没指定nextTab,那么将创建一个容量位两倍的节点数组
        	ps:这里通过try的方式来防止数组下标超标。
        */
        if (nextTab == null) {            // initiating
            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;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        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))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & 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;
                                }
                            }
                            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;
                        }
                    }
                }
            }
        }
    }

你可能感兴趣的:(java知识体系,java)