Java-ConcurrentHashMap源码解析

一、ConcurrentHashMap在JDK1.7和JDK1.8的区别

1.实现上的区别

在JDK1.7中采用的是Segment+HashEntry+Unsafe的实现


1.7.png

在JDK1.8中采用的是synchronized+CAS+Node+Unsafe的实现


1.8.png
ConcurrentHashMap在JDK1.7和1.8的区别:

(1)从结构上看,在JDK1.8中,ConcurrentHashMap的结构与HashMap基本类似
(2)从1.7到1.8的版本中,由于HashEntry从链表变成了红黑树,所以ConcurrentHashMap的时间复杂度从O(n)变成了O(log(n))
(3)HashEntry在1.8中称为Node,链表转红黑树的值是8,当Node链表的节点数大于8时Node会自动转化为TreeNode,会转换成红黑树的结构。不过这里链表长度超过8的时候,桶的数量必须要大于64。而当红黑树的数量小于6的时候,又会转换为链表
(4)在JDK1.7采用的是分段锁的概念,即Segment都是继承自ReentrantLock(可重入锁),但是在JDK1.8中采用的是synchronized+CAS的做法

使用CAS+synchronized替代分段锁的原因:
  • 粒度降低了;
  • JVM 开发团队没有放弃 synchronized,而且基于 JVM 的 synchronized 优化空间更大,更加自然。
  • 在大量的数据操作下,对于 JVM 的内存压力,基于 API 的 ReentrantLock 会开销更多的内存。
ConcurrentHashMap的并发度

程序运行时能够同时更新ConcurrentHashMap且不产生锁竞争的最大线程数。默认是16,且在构造器中可以设置。
当用户设置并发度时,ConcurrentHashMap 会使用大于等于该值的最小2幂指数作为实际并发度(假如用户设置并发度为17,实际并发度则为32)

    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

    /**
    * 得到一个大于c,且最接近c的2的幂次方值
    */
    private static final int tableSizeFor(int c) {
        // 对c-1得到n,然后每一次都对n先做或操作,再做无符号右移操作
        // 因为int类型为32位,所以计算到16就全部结束了,最终得到的就是最高位及其以下的都为1,
        // 这样就能保证得到的结果肯定大于或等于原来的n且为奇数,最后再加上1
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

在ConcurrentHashMap的构造器中,其并发度sizeCtl的设置,主要是通过tableSizeFor来进行,分析上面的源码,就可以知道为什么设置的时候,如果是17,其并发度其实是32。
这里的并发度,其实就是ConcurrentHashMap的table数组的数量,即有多少个链表。简单理解的话

CAS(compare and swap)

CAS是采用的非阻塞同步这样的乐观策略。
CAS指令需要有3个操作数:内存位置、旧的预期值、和新值。
CAS执行指令时,首先会从内存中取出旧值,接着通过计算获取到一个新值,再比较之前取出的旧值是否与内存中的该变量值一致,如果一致,将旧值swap为新值。如果之前取出的旧的预期值与内存中该变量的值不一致,则不执行更新操作,并且会重新获取旧值,在进行一次CAS指令操作。不过CAS不管是否执行更新操作,都会返回旧值。
因为使用悲观锁,需要频繁的进行上下文切换,而上下文切换很消耗性能,一次上下文切换大概会消耗3-5ms,而执行一条指令的时间大概只要0.6ns
线程一次拿锁的过程,至少是执行了两次上下文切换。这是因为悲观锁(synchronized)线程会主动进入阻塞状态,进入阻塞状态,那么要切换回来,就需要把执行状态的线程切换成其他状态,而把阻塞状态的切换成执行状态,这就两次切换;而乐观锁中线程并不会主动进入阻塞状态。

CAS的问题:

ABA问题:其实就是比如:线程1在比较之前,线程2把变量值变成了其他值,又变回来了,这个时候线程1去比较的时候,这个值依然是原来的,所以线程1会认为这个值没有变化过。但是实际上这个值已经被修改过。
开销问题:当比较的时候值一直不相等,那么会一直再来一次,当一个线程自旋,长期的不成功,对CPU的性能开销是很大的。
只能保证一个共享变量的原子操作:因为CAS是要比较内存中某个变量的值,一个地址保存一个变量,这样的话,CAS就无法比较多个变量的值,无法同时修改,想要同时的话,就需要多次的CAS,也就需要多次的自旋,CAS就不大能适应。

二、基于JDK1.8源码解析

1.常用的常量解析

    /**
     * 定义的最大表容量,由于32位hash值高两位用于控制的,所以是30位。2的30次方
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认的初始table表容量,这里为16,这个是必须是2的幂次方
     */
    private static final int DEFAULT_CAPACITY = 16;

    /**
     * 最大可能的table数组大小,toArray和其相关方法需要这个值
     */
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 默认的并发级别,其实这个与DEFAULT_CAPACITY类似
     */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * 负载因子,默认是0.75
     */
    private static final float LOAD_FACTOR = 0.75f;

    /**
     * 链表转换为红黑二叉树的最小临界点,当链表长度大于8时,链表转换为红黑二叉树
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 将红黑二叉树转换为链表的临街点,必须要小于TREEIFY_THRESHOLD
     * 并且这个值最多是6的时候就需要转换为链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 对table capacity进行红黑二叉树化的最小容量,该值应至少为4*TREEIFY_THRESHOLD,
     * 以避免调整大小和树化阈值之间的冲突。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 扩容线程每次最少要迁移的桶数量
     */
    private static final int MIN_TRANSFER_STRIDE = 16;

    /**
     * sizeCtl中用于生成标记的位数。 对于32位阵列,必须至少为6。
     */
    private static final int RESIZE_STAMP_BITS = 16;

    /**
     * 可以帮助调整大小的最大线程数。 必须为32-RESIZE_STAMP_BITS位。
     */
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

    /**
     * 在sizeCtl中记录大小标记的位移位。
     */
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    // forwarding nodes的hash值
    static final int MOVED     = -1;
    // 树根节点的hash值
    static final int TREEBIN   = -2;
    // ReservationNode的hash值
    static final int RESERVED  = -3;
    // 可用处理器数量
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    //存放node的数组
    transient volatile Node[] table;
    /*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
     *当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
     *当为0时:代表当时的table还没有被初始化
     *当为正数时:表示初始化或者下一次进行扩容的大小,这个值是table.length*0.75
    */
    private transient volatile int sizeCtl;

2.table的初始化

private final Node[] initTable() {
  Node[] tab; int sc;
  //每次循环都获取最新的Node数组引用
  while ((tab = table) == null || tab.length == 0) {
    //sizeCtl是一个标记位,若为-1也就是小于0,代表有线程在进行初始化工作了
    if ((sc = sizeCtl) < 0)
      //让出CPU时间片
      Thread.yield(); // lost initialization race; just spin
    //CAS操作,将本实例的sizeCtl变量设置为-1
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
      //如果CAS操作成功了,代表本线程将负责初始化工作
      try {
        //再检查一遍数组是否为空
        if ((tab = table) == null || tab.length == 0) {
          //在初始化Map时,sizeCtl代表数组大小,默认16
          //所以此时n默认为16
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          //Node数组
          Node[] nt = (Node[])new Node[n];
          //将其赋值给table变量
          table = tab = nt;
          //通过位运算,n减去n二进制右移2位,相当于乘以0.75
          //例如16经过运算为12,与乘0.75一样,只不过位运算更快
          sc = n - (n >>> 2);
        }
      } finally {
        //将计算后的sc(12)直接赋值给sizeCtl,表示达到12长度就扩容
        //由于这里只会有一个线程在执行,直接赋值即可,没有线程安全问题
        //只需要保证可见性
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

在这里,table使用了volatile关键字修饰,用来保证每次获取到的都是最新的数组,不能保证写入的是最新的值,想要保证是最新的值,其实是通过Node来保证。
sizeCtl也是一个volatile变量,它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性由volatile保证。
CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功
另外,因为table使用volatile修饰,只能保证该数组的地址是volatile的,值不能被保证,所以table中的值其实是通过Node中的val和next来保证的

    static class Node implements Map.Entry {
        final int hash;
        final K key;
        volatile V val;
        volatile Node next;

        Node(int hash, K key, V val, Node 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 Helpers.mapEntryToString(key, val);
        }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry e;
            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 find(int h, Object k) {
            Node 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;
        }
    }

在ConcurrentHashMap被初始化的时候,并不会调用initTable初始化table数组,而initTable数组其实是在第一次调用putVal的时候进行初始化的。

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node[] tab = table;;) {
            Node f; int n, i, fh;
            //判断Node数组为空
            if (tab == null || (n = tab.length) == 0)
                //初始化Node数组
                tab = initTable();
          ...
}

3.ConcurrentHashMap.put()方法

其实就是调用的putVal()方法

final V putVal(K key, V value, boolean onlyIfAbsent) {
  if (key == null || value == null) throw new NullPointerException();
  //对key的hashCode进行散列
  int hash = spread(key.hashCode());
  int binCount = 0;
  //一个无限循环,直到put操作完成后退出循环
  for (Node[] tab = table;;) {
    Node f; int n, i, fh;
    //当Node数组为空时进行初始化
    if (tab == null || (n = tab.length) == 0)
      tab = initTable();
    //Unsafe类volatile的方式取出hashCode散列后通过与运算得出的Node数组下标值对应的Node对象
    //此时的Node对象若为空,则代表还未有线程对此Node进行插入操作
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
      //直接CAS方式插入数据(往tab数组插入数据的时候)
      if (casTabAt(tab, i, null,
                   new Node(hash, key, value, null)))
        //插入成功,退出循环
        break;                   // no lock when adding to empty bin
    }
    //查看是否在扩容,先不看,扩容再介绍
    else if ((fh = f.hash) == MOVED)
      //帮助扩容
      tab = helpTransfer(tab, f);
    else {
      V oldVal = null;
      //对Node对象进行加锁
      synchronized (f) {
        //二次确认此Node对象还是原来的那一个,如果不是,则不执行if语句,那么synchronized中的内容就全部不会执行
        if (tabAt(tab, i) == f) {
          if (fh >= 0) {
            binCount = 1;
            //无限循环,直到完成put
            for (Node e = f;; ++binCount) {
              K ek;
              //和HashMap一样,先比较hash,再比较key,如果key的地址相等或者值相等则表示已经存在
              if (e.hash == hash &&
                  ((ek = e.key) == key ||
                   (ek != null && key.equals(ek)))) {
                oldVal = e.val;
                if (!onlyIfAbsent)
                  e.val = value;
                break;
              }
              Node pred = e;
              // 节点不存在,其实就是添加在链表的末尾
              if ((e = e.next) == null) {
                //和链表头Node节点不冲突,就将其初始化为新Node作为上一个Node节点的next
                //形成链表结构
                pred.next = new Node(hash, key,
                                          value, null);
                break;
              }
            }
          }
          // 如果是红黑树节点
          else if (f instanceof TreeBin) {
                            Node p;
                            binCount = 2;
                            if ((p = ((TreeBin)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
          }
          else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
        }
      }
      // 如果链表的长度大于等于最小转换红黑树的节点个数即8个
      // 将给定的节点索引替换所有链表中的节点
      if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
}

这里首先需要注意的是hash的计算,其实采用的是与HashMap一样的方式,都是通过key的hashCode的高16位异或低16位实现的。这样做的目的是保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。

    // 获取hash,这里的h是传入key.hashCode()
    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

其次需要注意的是tabAt(tab, i)方法,其使用了Unsafe类volatile的操作volatile式的查看值,用以保证每次获取的值都是最新的。这是因为Unsafe可以直接获取指定内存的数据,保证了每次拿到数据都是最新的。

    static final  Node tabAt(Node[] tab, int i) {
      return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

虽然上面的table变量加了volatile,但也只能保证其引用的可见性,并不能确保其数组中的对象是否是最新的,所以需要Unsafe类volatile式地拿到最新的Node
索引定位:

// n为桶的个数,即table的长度
(n - 1) & hash

put方法总结:
如果没有初始化就先调用initTable()方法来进行初始化过程,其中sizeCtl是标志位,初始由CAS操作将之置为-1,表示正在初始化
如果没有hash冲突就直接CAS插入
如果还在进行扩容操作就先进行扩容
如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环(阿里面试官问题,默认的链表大小,超过了这个值就会转换为红黑树);
如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容

4.ConcurrentHashMap.get()

//会发现源码中没有一处加了锁
public V get(Object key) {
    Node[] tab; Node e, p; int n, eh; K ek;
    int h = spread(key.hashCode()); //计算hash
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {//读取首节点的Node元素
        if ((eh = e.hash) == h) { //如果该节点就是首节点就返回
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //hash值为负值表示正在扩容,这个时候查的是ForwardingNode的find方法来定位到nextTable来
        //eh=-1,说明该节点是一个ForwardingNode,正在迁移,此时调用ForwardingNode的find方法去nextTable里找。
        //eh=-2,说明该节点是一个TreeBin,此时调用TreeBin的find方法遍历红黑树,由于红黑树有可能正在旋转变色,所以find里会有读写锁。
        //eh>=0,说明该节点下挂的是一个链表,直接遍历该链表即可。
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        while ((e = e.next) != null) {//既不是首节点也不是ForwardingNode,那就往下遍历
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

5.ConcurrentHashMap扩容

如果新增节点之后,所在的链表的元素个数大于等于8,则会调用treeifyBin把链表转换为红黑树。在转换table结构时,即treeifyBin,若table的长度小于MIN_TREEIFY_CAPACITY=64,则会将数组的长度扩大到原来的两倍,并触发transfer,重新调整节点位置。(只有当tab.length >= 64, ConcurrentHashMap才会使用红黑树。)具体可以看putVal中关于binCount最后的判断,调用treeifyBin方法,其内部的处理
新增节点后,addCount统计tab中的节点个数大于阈值(sizeCtl),会触发transfer,重新调整节点位置。

(1)ConcurrentHashMap.addCount
    private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        //首先if判断counterCells为空并且对当前map对象cas操作baseCount + x成功,就跳过if里的操作,
        //因为都cas操作baseCount + x成功了,就不需要通过counterCells辅助了,简单明了。
        // 如果counterCells为null或者baseCount+x的cas操作失败
        // 则进入if条件
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            //如果counterCells为空,直接执行fullAddCount。
            //如果不为空,判断当前线程在counterCells中的槽位是否为空,如果不为空,
            //对槽位中的CounterCell对象cas操作value加1,成功return,失败执行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))) {
                // cas操作失败,则会执行fullAddCount方法,cas主要是多线程操作的时候
                // 操作失败的线程会执行fullAddCount。
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
        // 检查是否扩容
        if (check >= 0) {
            Node[] tab, nt; int n, sc;
            // 节点个数大于阈值sizeCtl,并且table为不null,table的长度小于最大的容量
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                // 调整table的长度n,用于标识当前的size。
                //生成一个戳 算法是Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
                //如果n=16,则生成的值为0000000000000000010000000000011011(27和2的15次方相或)    
                int rs = resizeStamp(n);
                // sc<0其实就是sizeCtl<0,表示正在进行扩容
                if (sc < 0) {
                    // 其他线程正在进行扩容时,break
                    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();
            }
        }
    }
(2)ConcurrentHashMap.transfer
    // 复制元素到nextTab
    private final void transfer(Node[] tab, Node[] nextTab) {
        int n = tab.length, stride;
        // 在这里NCPU是CPU的核心数,每个CPU核心平均分配复制任务
        // stride就表示每个线程进来时需要复制的量,如果stride小于最小复制的大小即16,则赋值为16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        // nextTab为null,初始化nextTab,先对原table的基础上扩容一倍
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node[] nt = (Node[])new Node[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            // 记录nextTable,用于下一次resize
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        // 构造一个forward节点,用于存放不需要复制的节点
        ForwardingNode fwd = new ForwardingNode(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        //通过for自循环处理每个槽位中的链表元素,默认advace为真,
        //通过CAS设置transferIndex属性值,并初始化i和bound值,i指当前处理的槽位序号,
        //bound指需要处理的槽位边界,即下限,这里是先处理槽位15的节点;
        // 无限for循环,i和bound的值在while循环中处理得到
        // for循环的结束,由if (i < 0 || i >= n || i + n >= nextn) 决定,满足该条件才结束for循环的复制过程
        for (int i = 0, bound = 0;;) {
            Node f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                // --i是转移表
                // 比如第一次while循环,i=0,bound=0,transferIndex=tab.length
                // 所以前面两个if条件都不会满足,直接进入第三个if条件,cas操作
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                // 比较this对象中TRANSFERINDEX偏移量位置的值与nextIndex做比较
                // 如果相等,就把nextBound赋值给TRANSFERINDEX偏移量位置的值
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    //第一次进入的时候如果到达了这里则接到了当前应该进行的任务:负责搬运[nextBound:i]之间的内容
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            // 线程执行到这里,table复制过程结束
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //如果所有的节点都已经完成复制工作  就把nextTable赋值给table 清空临时对象nextTable  
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    //扩容阈值设置为原来容量的1.5倍  依然相当于现在容量的0.75倍。因为扩容因子是0.75
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                // 使用cas方式更新扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作
                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
                }
            }
            //如果遍历到的节点为空 则放入ForwardingNode指针 
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            // 如果遍历到了forward节点,说明已经被处理过了,则跳过
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            // 具体的搬运复制过程
            else {
                // 针对Node链表添加锁,这里做了双重检索
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node ln, hn;
                        // 链表节点
                        if (fh >= 0) {
                            // resize后的元素要么在原地,要么移动n位(n为原capacity)
                            // 对fh链表节点做与操作
                            int runBit = fh & n;
                            Node lastRun = f;
                            for (Node 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 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(ph, pk, pv, ln);
                                else
                                    hn = new Node(ph, pk, pv, hn);
                            }
                            // 在nextTab的i位置插入一个链表
                            setTabAt(nextTab, i, ln);
                            // 在nextTab的i+n位置插入一个链表
                            setTabAt(nextTab, i + n, hn);
                            // 将tab中的i位置置为fwd,则tab中i位置的节点已经被复制
                            setTabAt(tab, i, fwd);
                            // 置为true,继续下一次for循环,并且在最初的while执行
                            advance = true;
                        }
                        // 如果f是树形节点
                        else if (f instanceof TreeBin) {
                            TreeBin t = (TreeBin)f;
                            TreeNode lo = null, loTail = null;
                            TreeNode hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode p = new TreeNode
                                    (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;
                                }
                            }
                            //(1)如果lo链表的元素个数小于等于UNTREEIFY_THRESHOLD,默认为6,
                            // 则通过untreeify方法把树节点链表转化成普通节点链表;
                            //(2)否则判断hi链表中的元素个数是否等于0:如果等于0,表示lo链表中包含了所有原始节点,
                            // 则设置原始红黑树给ln,否则根据lo链表重新构造红黑树。
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

6.ConcurrentHashMap统计个数

ConcurrentHashMap中的元素个数,是由baseCount和CounterCell数组中每个元素的value值之和得到的。这样做的原因是,当多个线程同时执行CAS修改baseCount值,失败的线程会将值放到CounterCell中。

    /**
     * 基本计数器值,主要在没有争用时使用。 通过CAS更新。
     */
    private transient volatile long baseCount;
    /**
     * table计数单元数组,大小是2的次幂
     */
    private transient volatile CounterCell[] counterCells;

    static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }
    public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }


    final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

三、参考

ConcurrentHashMap是如何实现线程安全的

你可能感兴趣的:(Java-ConcurrentHashMap源码解析)