JDK类库源码分析系列3--集合类分析(10) ConcurrentHashMap

190 阅读20分钟

​ 我们知道ConcurrentHashMap是与HashMap相比其是线程安全的,其的原理主要是通过CAS来对对应变量进行修改,同时synchronized来对并发时的一些主要逻辑进行锁操作,其锁的对象是数组对应槽位的第一个元素(也就是锁住了对应槽位的整条链表),下面我们就来具体分析下其实现线程安全的原理。梳理一些主要的逻辑内容,建议在阅读本篇前阅读下这个系列的第8篇关于HashMap的实现,对Map结构的存储逻辑有个整体的介绍,这篇主要是梳理下重点方法对并发的处理。

一、结构

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {

可以看到其继承于类AbstractMap,同时实现ConcurrentMap接口(这个接口相对来说并没有特别的)。

二、构造方法

public ConcurrentHashMap() {
}
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;
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}
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;
}

​ 这里可以看到就是对扩容阈值sizeCtl的初始化计算,同时上面的tableSizeFor方法我们在HashMap中也有梳理,就是用来计算进位的。

三、变量

1、MAXIMUM_CAPACITY

private static final int MAXIMUM_CAPACITY = 1 << 30;

​ 最大的容量。

2、DEFAULT_CAPACITY

private static final int DEFAULT_CAPACITY = 16;

默认的容量。

3、MAX_ARRAY_SIZE

static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

​ 最大的数组长度。

4、DEFAULT_CONCURRENCY_LEVEL

private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

​ 默认的并发级别,现在没有使用了(1.8 & +),这个好像是因为以前是使用Segment这种结构?

5、LOAD_FACTOR

private static final float LOAD_FACTOR = 0.75f;

​ 扩容的加载因子,等同于HashMap的该字段。

6、MOVED

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

​ MOVED表示正在扩容。

7、NCPU

static final int NCPU = Runtime.getRuntime().availableProcessors();

​ 当前系统的CPU的核数量。

​ 这上面省略了一些关于树结构相关的变量。

8、table

transient volatile Node<K,V>[] table;

​ 类似于HashMap的Node数组,不过其前面加了volatile,来实时感知其的变化。

9、nextTable

/**
 * The next table to use; non-null only while resizing.
 */
private transient volatile Node<K,V>[] nextTable;

​ 下一个数组,这个是用来处理并发扩容的。

10、baseCount

private transient volatile long baseCount;

​ 用来对当前Map放了多少元素进行计数。

11、sizeCtl

private transient volatile int sizeCtl;

​ 对数组扩容的控制,达到该值的元素容量就进行扩容。同时这个字段还有其他的意思。当小于0的时候:如果为-1表明其正在进行扩容,其他更小的是( 1 + 正在调整线程扩容的线程数量),但好像JDK源码的注释是有问题的(或者是翻译的问题?),这个其实要将其往后移动16位才是其要表达的意思。

12、transferIndex

private transient volatile int transferIndex;

​ 我们知道ConcurrentHashMap是用来处理并发的,所以在扩容的时候其也是使用分段,用多线程(如果存在并发的话)来将对应段的数据转移到新数组中(其是从后往前的),这个transferIndex表明下一个线程要扩容的话是从哪个位置开始。

13、cellsBusy

/**
 * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
 */
private transient volatile int cellsBusy;

​ 这个是在扩容用来自旋操的。

14、CounterCell

private transient volatile CounterCell[] counterCells;
@jdk.internal.vm.annotation.Contended static final class CounterCell {
    volatile long value;
    CounterCell(long x) { value = x; }
}

​ 这是用来看那些扩容计算是通过多线程处理的。这里的@jdk.internal.vm.annotation.Contended,是一个缓存行的概念,可以搜索其他的博文了解。value就是用来计算使用的。

15、MAX_RESIZERS

/**
 * The maximum number of threads that can help resize.
 * Must fit in 32 - RESIZE_STAMP_BITS bits.
 */
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

​ 能帮助扩容的最大的线程数量。

16、RESIZE_STAMP_BITS&RESIZE_STAMP_SHIFT

private static final int RESIZE_STAMP_BITS = 16;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

​ 这两个其值都是16,但其使用的场景不同。这个需要结合sizeCtl来梳理。RESIZE_STAMP_BITS是左移、RESIZE_STAMP_SHIFT是右移。

17、CAS相关操作的变量

private static final Unsafe U = Unsafe.getUnsafe();
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final int ABASE;
private static final int ASHIFT;

static {
    try {
        SIZECTL = U.objectFieldOffset
            (ConcurrentHashMap.class.getDeclaredField("sizeCtl"));
        TRANSFERINDEX = U.objectFieldOffset
            (ConcurrentHashMap.class.getDeclaredField("transferIndex"));
        BASECOUNT = U.objectFieldOffset
            (ConcurrentHashMap.class.getDeclaredField("baseCount"));
        CELLSBUSY = U.objectFieldOffset
            (ConcurrentHashMap.class.getDeclaredField("cellsBusy"));

        CELLVALUE = U.objectFieldOffset
            (CounterCell.class.getDeclaredField("value"));

        ABASE = U.arrayBaseOffset(Node[].class);
        int scale = U.arrayIndexScale(Node[].class);
        if ((scale & (scale - 1)) != 0)
            throw new Error("array index scale not a power of two");
        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
    } catch (ReflectiveOperationException e) {
        throw new Error(e);
    }

    // Reduce the risk of rare disastrous classloading in first call to
    // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
    Class<?> ensureLoaded = LockSupport.class;
}

​ 可以看到这里是通过Unsafe类来进行CAS原子操作的。

四、内部类

1、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;

    Node(int hash, K key, V val) {
        this.hash = hash;
        this.key = key;
        this.val = val;
    }

​ 这个其实与HashMap的结构以及变量定义,不过这里不同的是valnext前面加了valatile关键词来处理并发,如果有并发就能实时感知这个值的变化。

2、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);
        this.nextTable = tab;
    }
...
    Node(int hash, K key, V val) {
            this.hash = hash;
            this.key = key;
            this.val = val;
        }

​ 将要扩容那段数组构建为ForwardingNode,,可以看到其的key是设置的MOVED的。

五、主要方法

1、tabAt(Node<K,V>[] tab, int i)

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

​ 获取tabi位置的值。

2、casTabAt(...)

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

​ 使用CAS来设置tab对应i位置的值,如果是预期值c就将其设置为v

3、setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectRelease(tab, ((long)i << ASHIFT) + ABASE, v);
}

​ 这个方法也是在指定位置i设置对应v的值,不过其没有比较再设置(看逻辑,对其的调用都是在synchronized中)。

4、spread(int h)

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

​ 再计算h的hash值。

5、putVal(K key, V value, boolean onlyIfAbsent)

public V put(K key, V value) {
    return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
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<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent && fh == hash &&  // check first node
                 ((fk = f.key) == key || fk != null && key.equals(fk)) &&
                 (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

这个就是其的put方法,可以看到ConcurrentHashMap是不能添加key&value为null的元素,现在我们来具体分析其的逻辑(省略关于红黑树的处理逻辑)。

​ 1)、首先是通过spread(key.hashCode())方法对key再计算一次对应的Hash值。

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

​ 2)、binCount = 0;这个变量是用来记录当前槽位的链表的长度的。

​ 3)、然后再遍历Node<K,V>[] tab = table;;,可以看到这个for循环的添加部分与自增部分是没有值的,就表名其是一种等同于自旋的操作,依赖内部的returnbreak这样的跳出逻辑。

for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; int n, i, fh; K fk; V fv;

​ 这里的fhfkfv指的是前面对应的f局部变量的hash、key、value值。

​ 4)、首先如果table还没有进行初始化,就通过initTable方法对其进行初始化。

if (tab == null || (n = tab.length) == 0)
    tab = initTable();

​ 5)、首先是判断在tabletab, i = (n - 1) & hash)位置是否已经有数组值了,如果没有,就通过casTabAt方法去设置,可以看到其的预期值是null,如果返回true就表明其是成功的,就能通过break跳出逻辑。

​ 这这个地方涉及到对成员变量table的修改,所以我们要考虑到并发。这里我们在前面有提过volatile Node<K,V>[] table是有加valitile关键字的,但valitile是可见性以及禁止重排序,而并没有原子操作。这里在设置定义i位置的值的时候,是先判断有没有值(并发可能其它线程已经添加了),再进行对应的赋值,这是两个操作,valitile关键字并没有原子性,所以就需要使用casTabAt来进行原子操作赋值,(如果这个时候其他线程已经将该位置赋值了,此线程比其他线程慢了,所以此线程的这个节点就需要添加在那个节点的下一个节点)。如果赋值失败,则进行下一次循环。

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;                   // no lock when adding to empty bin
}

​ 6)、如果这个节点的hash值是MOVED(通过第5)进行的赋值(表明数组该位置已经有值了,这个时候获取的是链表的第一个节点),前面提过,表明正在进行扩容,就加入helpTransfer来帮助其他线程一起来扩容。

else if ((fh = f.hash) == MOVED)
    tab = helpTransfer(tab, f);

​ 7)、然后如果该位置有值了,并且也没有进行扩容,就判断这个位置的节点与要添加的节点是否相等并通过onlyIfAbsent判断是否只在不存在的时候才进行添加,如果是,就这个返回这个已经存在节点的原来的值。

else if (onlyIfAbsent && fh == hash &&  // check first node
         ((fk = f.key) == key || fk != null && key.equals(fk)) &&
         (fv = f.val) != null)
    return fv;

​ 8)、当上面都不满足了,就加入正式的添加逻辑。在这里就使用了synchronized关键字来加锁,其锁住的是ftable数组该位置的节点**(链表的第一个添加的节点,然后每次for循环的实时都是获取的数组index位置,所以这样就锁住了整个链表)**,通过前面一系列的判断来尽量缩小锁的颗粒。

V oldVal = null;
synchronized (f) {

​ 9)、再通过CAS获取一次tab数组i位置的值是不是我们原来获取的第一个元素(这个线程已经上锁了),因为可能其他线程再我们上锁前就将其改变了,例如将这个节点删除了或者改变位置了。在判断节点f的hash值还是不是>=0,因为如果扩容这个hash值是会被修改为MOVEN-1。

if (tabAt(tab, i) == f) {
    if (fh >= 0) {

​ 10)、对binCount自增,也起到记录当前位置链表的节点个数。找到当前位置链表的最后一个位置,然后进行赋值pred.next = new Node<K,V>(hash, key, value);逻辑。

if (fh >= 0) {
    binCount = 1;
    for (Node<K,V> e = f;; ++binCount) {
        K ek;
        if (e.hash == hash &&
            ((ek = e.key) == key ||
             (ek != null && key.equals(ek)))) {
            oldVal = e.val;
            if (!onlyIfAbsent)
                e.val = value;
            break;
        }
        Node<K,V> pred = e;
        if ((e = e.next) == null) {
            pred.next = new Node<K,V>(hash, key, value);
            break;
        }
    }
}

​ 11)、下面关于TreeBin的逻辑我们目前跳过。

​ 12)、前面是将这个put的元素添加到对应数组+链表的对应位置。但我们还要处理扩容逻辑&确保添加的这个元素能记录到统计添加元素个数的成员变量中。

addCount(1L, binCount);
return null;

​ 下面我们就来具体分下上面关于添加元素方法的其他调用方法。

6、initTable()

/**
 * Initializes table, using the size recorded in sizeCtl.
 */
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
            try {
                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;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

​ 这个就是其的扩容方法。可以看到:

​ 1)、首先是判断当前table有没有进行初始化创建赋值,没有的话才进入初始化逻辑。这里的初始化与HashMap也是有部分不同,ConcurrentHashMap的初始化&扩容是完全独立的两个方法。

​ 2)、再判断(sc = sizeCtl) < 0是否小于0,因为可能其他的线程也在进行初始化创建。如果竞争失败,就通过Thread.yield()方法让出CPU,等待CPU的下次调度,下次的时候,再进行while循环(tab = table) == null,如果其他的线程初始化创建成功了,就不进行下面逻辑,直接return tab;

​ 3)、如果当前线程通过CAS竞争成功,将SIZECTL修改为了-1,其就会在上一步进入yield状态。下面就是当前线程的初始化数组状态。

​ 4)、同时在里面也进行了一次((tab = table) == null检查。双重检查,类似于单例模式的创建对象。这里我刚才还在想一种转态,就是如果其他线程已经完成了初始化了,刚好完成下面的finally里面的sizeCtl = sc;,这个时候sizeCtl就不为-1,并且这个时候刚好当前线程走到if ((sc = sizeCtl) < 0)这里就会不成功,然后在CAS也能成功进入,会存在重复创建的内容。由于双重检查以及table是有volatile修饰的,就解决了重复创建的问题。同时我们再进行单例创建的时候也可以借鉴这里的这种写法。

​ 5)、这里对数组的初始化的长度是看构造方法对sizeCtl的赋值情况,如果没有就算DEFAULT_CAPACITY,然后就是扩容阈值的赋值了,sc = n - (n >>> 2);sizeCtl = sc;。可以看到这个sizeCtl是有多个含义,并不像HashMap的阈值那样只表明扩容阈值这一个含义。

6、sumCount()

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;
}

​ 这个方法就是用来计算当前Map总的元素个数,可以看到其的计算是baseCountcounterCells数组的value和。

7、addCount(long x, int check)

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    if ((as = counterCells) != null ||
        !U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSetLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSetInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

​ 这个方法主要是有两个逻辑,计算元素的个数&扩容。同时这个入参对于putVal方法来说,其传入的是对应链表的元素个数,所以其是>0的。

​ 1)、这里最开始的逻辑是判断counterCells,为不为空。如果为空,就表示还没有发生线程之间的竞争。有没有竞争再是通过后面的!U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x),判断的,如果设置失败,就表示有竞争。如果没有竞争失败,就通过CAS对baseCount成员变量进行进行设值也就是+1,对于putVal方法addCount(1L, binCount);

CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
    !U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {

​ 2)、如果竞争失败了(这个数量就没有计算到,就会将这个数据计算设置到CounterCell中)。这里首先是判断as也就是counterCells有没有被初始化,没有或者当前线程对应的CounterCell为空,这个ThreadLocalRandom.getProbe()与线程相关的一个值,可以简单理解为其是与线程相关的一个固定的hash值,如果没有对其进行初始化,其的默认值为0

​ 再或者已经存在当前线程对应的CounterCell了,就对其的值进行+1,也就是表示这个元素的计算是加在了当前线程对应的CounterCell中了。所以要计算ConcurrentHashMap中的元素,是需要baseCount+CounterCell数组中的value和的。这个if都不满足,例如没有竞争就直接CAS对CELLVALUE设值成功了,就会s = sumCount();计算获取总的元素值(check>1),不然是直接return。

CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
    (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
    !(uncontended =
      U.compareAndSetLong(a, CELLVALUE, v = a.value, v + x))) {
    fullAddCount(x, uncontended);
    return;
}
if (check <= 1)
    return;
s = sumCount();

​ 3)、这个就是检查需不需要扩容。如果多线程的话,就可以通过别的线程来将其进行扩容。这里的逻辑了解起来有点费劲,有一个地方我目前还没有理解sc == rs + 1,这个等式应该一直不会等(我在测试的时候也没有出现这个情况,有大佬看到 的话麻烦赐教)。下面我们就来具体分析下。

if (check >= 0) {
    Node<K,V>[] tab, nt; int n, sc;
    while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
           (n = tab.length) < MAXIMUM_CAPACITY) {
        int rs = resizeStamp(n);
       ......
    }
}
static final int resizeStamp(int n) {
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

​ 首先我们需要明白sizeCtl<-1 那部分表示目前有多少个线程在帮助扩容的含义(我们前面有提过需要将其往右移16位)。

​ 1、先看resizeStamp方法,这个(1 << (RESIZE_STAMP_BITS - 1)其的二进制表示就是1000000000000000共16位其的int是32768。同时Integer.numberOfLeadingZeros(n)方法是看int类型32位n其前面有多少个0,例如如果n为16,则其前面就有27个0。所以这个方法的返回是在32768 -32768+32之间。不过由于默认16的关系,所以其一般应该是在32768+27之间。例如如果n为16,则这个方法返回的二进制是1000000000011011。然后对于线程等待计数10000000000110110000000000000010,用低16为来表示。

while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
       (n = tab.length) < MAXIMUM_CAPACITY) {
    int rs = resizeStamp(n);
    if (sc < 0) {
       ......
    }
    else if (U.compareAndSetInt(this, SIZECTL, sc,
                                 (rs << RESIZE_STAMP_SHIFT) + 2))
        transfer(tab, null);
    s = sumCount();
}

​ 2、然后回到while方法,s >= (long)(sc = sizeCtl)其中s在前面已经算过其实当前Map中的元素,这个while中的条件就是判断需不需要进行扩容(可以是帮助扩容 ,也可以是扩容的最前面的一个)。

​ 3、再通过int rs = resizeStamp(n);方法对rs进行赋值,这个局部变量的目的我的理解是:因为n表明的是tab数组的长度,所以其实rt就是来记录判断tab数组有没有完成扩容(扩容一般就会产生进位了)。

​ 4、现在我们来具体看下这个逻辑。

​ 这里假如现在没有多线程竞争,所以sc = sizeCtl是>0的走的是下面else if的逻辑。其对sizeCtl的赋值逻辑是:rs << RESIZE_STAMP_SHIFT + 2,这个其实就是将rs的值赋值到高16位(所以sizeCtl就会<0了),同时这个+2就相当于-2,所以这个时候扩容的线程就是|2|-1为1(因为sizeCtl在前面提过,-1表MOVEN)。然后再调用 transfer(tab, null)去扩容。

​ 如果有其它线程竞争,就会有两种情况。在else if竞争失败(本线程),由与其他线程现在再扩容(还未完成)。竞争失败,再次while,由于还未完成扩容,所以s >= (long)(sc = sizeCtl)是满足的。再进行if (sc < 0) {判断,就会满足。下面我们就来分析这个if中的内容。

while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
       (n = tab.length) < MAXIMUM_CAPACITY) {
    int rs = resizeStamp(n);
    if (sc < 0) {
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
            break;
        if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
            transfer(tab, nt);
    }
    e......
}

​ 5、这里的sc >>> RESIZE_STAMP_SHIFT其实就是获取原来的高16位的值,也就是竞争成功还没有完成扩容的那些线程是同一个值,判读与rs是不是相等(要理解前面写的关于rs的描叙解析,不知道我又没有描叙明白),如果不相等,就表明已经进位完成扩容了,就可以break了。

​ 这里还有剩余的4个或条件:sc == rs + 1这个我还没有明白这两个什么时候能相等?sc == rs + MAX_RESIZERS就是判断有没有达到帮助扩容的最大线程、(nt = nextTable) == null&transferIndex <= 0表明是不是不再需要其他线程的帮助了。如果还需要就通过CAS来修改sizeCtl表明又增加了一个线程来帮助扩容。

8、fullAddCount(long x, boolean wasUncontended)

// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
    int 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;
        if ((as = counterCells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {            // Try to attach new Cell
                    CounterCell r = new CounterCell(x); // Optimistic create
                    if (cellsBusy == 0 &&
                        U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
                        boolean created = false;
                        try {               // Recheck under lock
                            CounterCell[] rs; int m, j;
                            if ((rs = counterCells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            else if (U.compareAndSetLong(a, CELLVALUE, v = a.value, v + x))
                break;
            else if (counterCells != as || n >= NCPU)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            else if (cellsBusy == 0 &&
                     U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
                try {
                    if (counterCells == as) {// Expand table unless stale
                        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
            }
            h = ThreadLocalRandom.advanceProbe(h);
        }
        else if (cellsBusy == 0 && counterCells == as &&
                 U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
            boolean init = false;
            try {                           // Initialize table
                if (counterCells == as) {
                    CounterCell[] rs = new CounterCell[2];
                    rs[h & 1] = new CounterCell(x);
                    counterCells = rs;
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        else if (U.compareAndSetLong(this, BASECOUNT, v = baseCount, v + x))
            break;                          // Fall back on using base
    }
}

​ 这个方法就是对counterCells的初始化,表明有多个线程竞争。

​ 1)、这先是对当前线程的getProbe的初始化ThreadLocalRandom.localInit();,再wasUncontended = true;表明没有竞争。

// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
    int h;
    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();      // force initialization
        h = ThreadLocalRandom.getProbe();
        wasUncontended = true;
    }

​ 2)、这里是for (;;),也就是依赖内部跳出。如果已经完成了(as = counterCells) != null的初始化,就看当前线程对应记录添加数有没有初始化(a = as[(n - 1) & h]) == nullcellsBusy是用来在for中自旋的。没有初始化就创建new CounterCell(x)

boolean collide = false;                // True if last slot nonempty
for (;;) {
    CounterCell[] as; CounterCell a; int n; long v;
    if ((as = counterCells) != null && (n = as.length) > 0) {
        if ((a = as[(n - 1) & h]) == null) {
            if (cellsBusy == 0) {            // Try to attach new Cell
                CounterCell r = new CounterCell(x); // Optimistic create
                ......
             }
             collide = false;

​ 3)、在CAS将cellsBusy修改为1,表明有其它的正在处理(后面对cellsBusy修改为1的时候,预期值都是为0,也就是锁住了其他修改,让其不能进入其他逻辑)。然后再再里面进行了一次判断 rs[j = (m - 1) & h] == null,再将其赋值rs[j] = r,如果已经成功了created = true,再将cellsBusy = 0,复原,最后 break;,如果不满足判断条件、或者赋值失败,则continue,下次竞争。如果竞争失败,不会进入breakcontinue,就会collide = false;

if (cellsBusy == 0) {            // Try to attach new Cell
    CounterCell r = new CounterCell(x); // Optimistic create
    if (cellsBusy == 0 &&
        U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
        boolean created = false;
        try {               // Recheck under lock
            CounterCell[] rs; int m, j;
            if ((rs = counterCells) != null &&
                (m = rs.length) > 0 &&
                rs[j = (m - 1) & h] == null) {
                rs[j] = r;
                created = true;
            }
        } finally {
            cellsBusy = 0;
        }
        if (created)
            break;
        continue;           // Slot is now non-empty
    }
}
collide = false;

​ 4)、 如果(a = as[(n - 1) & h]) == null不满足,表明这个线程的CounterCell已经完成创建了。如果wasUncontendedfalse则将其wasUncontended = true;,继续下一个循环。先一个就会进入CAS修改CounterCell的计数了,如果竞争成功,就完成了本线程的竞争添加元素计数,然后break;

if ((as = counterCells) != null && (n = as.length) > 0) {
    if ((a = as[(n - 1) & h]) == null) {
        ......
    }
    else if (!wasUncontended)       // CAS already known to fail
        wasUncontended = true;      // Continue after rehash
    else if (U.compareAndSetLong(a, CELLVALUE, v = a.value, v + x))
        break;
    else if (counterCells != as || n >= NCPU)
        collide = false;            // At max size or stale
    else if (!collide)
        collide = true;
    else if (cellsBusy == 0 &&
             U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
        try {
            if (counterCells == as) {// Expand table unless stale
                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
    }
    h = ThreadLocalRandom.advanceProbe(h);
}

​ 如果失败,就counterCells != as查看其还是否相等。在else if (counterCells != as || n >= NCPU)后面字段我的理解是如果竞争太激烈了,就进行counterCells的扩容舒缓竞争压力。不知道有没有具体理解这里的含义,而导致认知错误?

​ 5)、这里就是如果还没有完成对counterCells初始化创建,则进行初始化创建,可以看到其的初始化的长度是new CounterCell[2]

for (;;) {
    CounterCell[] as; CounterCell a; int n; long v;
    if ((as = counterCells) != null && (n = as.length) > 0) {
        ......
    }
    else if (cellsBusy == 0 && counterCells == as &&
    U.compareAndSetInt(this, CELLSBUSY, 0, 1)) {
       boolean init = false;
       try {                           // Initialize table
           if (counterCells == as) {
               CounterCell[] rs = new CounterCell[2];
               rs[h & 1] = new CounterCell(x);
               counterCells = rs;
               init = true;
           }
       } finally {
           cellsBusy = 0;
       }
       if (init)
           break;
    }
    else if (U.compareAndSetLong(this, BASECOUNT, v = baseCount, v + x))
       break;      

9、transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)

/**
 * Moves and/or copies the nodes in each bin to new table. See
 * above for explanation.
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    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) {            // 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.compareAndSetInt
                     (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.compareAndSetInt(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) {
                        ......
                    }
                }
            }
        }
    }
}

​ 这个方法与前面的addCount方法一样也是比较复杂。我们可以看到相对来说其的插入元素的逻辑还是比较简单

​ 1)、首先是设置数据扩容的时候,分段的默认值

int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
    stride = MIN_TRANSFER_STRIDE; // subdivide range
private static final int MIN_TRANSFER_STRIDE = 16;

​ 2)、nextTab表明数据扩容的目的数组,这里也是进行默认的初始化,可以看到其是Node<?,?>[n << 1];,向左移动一位。同时、transferIndex表示的是下次(例如其他线程)帮助扩容,由这个地方的槽位开始往0靠(在前面提过,其实由后往前),同时由于这个方法的进入之前的addCount方法的逻辑,只有最开始才会nextTabnull,后面其他的线程进入就会有值了

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;
}

​ 3)、将nextTab包裹为ForwardingNode表示正在进行扩容,advance表示还需不需要往前推继续扩容,finishing表明是不是已经完成扩容了(不然进入到for循环中线程会一直再里面帮助扩容)。

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

​ 4)、首先看这个for结构,其没有写判断条件,只能依赖里面的returnbreak,不过这里是用的return,同时我们可以看到这个return有一个是由finishing控制的,同时finishing又是由下面的CAS设置控制的。

​ 其的进入条件首先是i < 0 || i >= n || i + n >= nextn,这个i表明的是数组的index,如果i小于0(表明已经遍历完成),或 i >= n n是原来tab数组的长度,亦或者i + n >= nextnnextn是新扩容的数组的长度,满足这些条件中的一个,就进入下面的可能return逻辑。

​ CAS对sizeCtl-1赋值(表明可能已经都分段扩容任务完成了,这个线程已经不能再帮助扩容了,将帮助扩容的线程数量-1),如果成功则判断(sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT,注意这里的顺序:<< 优先级比!=高,这里其实 就是有其它的线程还在继续数据转移到新数组,但当前线程已经没有新的端来让其处理了,也就会达到这个!=的逻辑,也就当前线程能return

​ 当最后一个线程完成了,这时候sc - 2就表示已经没有线程来帮助扩容了,就能达到与resizeStamp(n) << RESIZE_STAMP_SHIFT相等,也就会finishing = advance = true;&i = n;i为原来数组的长度),再下次for循环的时候就会if (finishing)满足,由这个最后的线程来完成sizeCtl,设置下次扩容的值sizeCtl = (n << 1) - (n >>> 1);并完成nextTable = null;。这样就借助所有的线程完成了数据转移到新的数组。

for (int i = 0, bound = 0;;) {
     ......
    if (i < 0 || i >= n || i + n >= nextn) {
        int sc;
        if (finishing) {
            nextTable = null;
            table = nextTab;
            sizeCtl = (n << 1) - (n >>> 1);
            return;
        }
        if (U.compareAndSetInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
            if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                return;
            finishing = advance = true;
            i = n; // recheck before commit
        }
    }
     ......
    else {
         ......
        }
    }
}

​ 下面我们就来看下具体是怎样分段多线程数据转移的。

​ 5)、advance默认为true,这里就是找到当前数据需要负责那段的数据转移。ibound默认为0,所以最开始 if (--i >= bound || finishing)这个是不满足的。bound指的是当前线程处理的界限(结束的位置,靠近0那端),i表示的是本线程处理端的开始,也就是transferIndex那边。前面我们在4)那部分分析了ifinishing的可能赋值,对于finishing来说其就会进入这里来下次进入后面的for逻辑退出。同时这个--i,其实也完成了自减,这里的--i >= bound就表明i,还在这个当前线程数据转移里面。

​ **同时我们需要注意,对于这段代码来说,是有多个线程再处理的。这些线程的ibound是不一样的,但都是划分在原来的tab设置长度之间的。**主要是要连接不同线程处理不同段的数据转移。

nextIndex的含义是当前线程处理的端的开始位置nextIndex = transferIndex,如果还能分配,这个就会>0,就不会跳出,就会往下进入对TRANSFERINDEX的修改,将其往后再移动,表明当前段被其承包了。如果已经没有更多段了,就进行i = -1;advance = false,来满足退出while以及if (i < 0 || i >= n || i + n >= nextn) 的条件来退出。

​ 现在我们来看下TRANSFERINDEX的CAS,这个stride是在前面赋值的,表明每段分多长,将下次的起点赋值给transferIndex,再将本线程负责的这段的结束赋值给bound,即下次的起点,在将本次的起点赋值给i = nextIndex - 1 i来进行循环遍历。

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.compareAndSetInt
                 (this, TRANSFERINDEX, nextIndex,
                  nextBound = (nextIndex > stride ?
                               nextIndex - stride : 0))) {
            bound = nextBound;
            i = nextIndex - 1;
            advance = false;
        }
    }

​ 6)、这里就是CAS获取当前i的第一个节点,如果为null,将这个位置用fwd表示,表明tab的这个index正在扩容,但由于没有值,所以没有数据转移的过程。

​ 然后判断(fh = f.hash) == MOVED,如果满足就表明正在扩容,则advance = true;,让当前线程去找下一段去处理。

​ 如果上面都不满足就表明需要进行数据转移了,可以看到这里是synchronized (f),锁住第一个,来达到锁住整条链表,同时在前面我们分析插入元素的时候,也会synchronized (f),这样在数据转移的时候就不能在本条链表中插入数据了。

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) {

​ 7)、可以看到在这里也进行了双重检查。同时这里数据的转移也涉及到低位&高位的概念,这部分就再赘叙了,可以去看下前面写的关于HashMap的分析,里面有具体分析。但这里有不同的是这里并不是用四个成员变量来按顺序对应构建高位。这里是int b = p.hash & n;runBit = b;(runBit的取值是会为0(低位,不需要+扩容的长度)或n(高位,需要+)),其表明的含义是如果上次与下次的高低位子不同就改变runBit。也就是说,如果是101011111这种顺序,就会到第5个位置的1停止。

​ 这样在下面的for (Node<K,V> p = f; p != lastRun; p = p.next)逻辑就会停止,不需要再创建新的节点来转移后面的节点,算是一种优化(需要满足后面是相同的高位或低位),但这里如果不满足在后面部分存在较多相同的高位或低位,不是多遍历了一次?(亦或者是我的理解由问题?)

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;
        }

​ 然后是通过setTabAt方法将其赋值到扩容的数组中。并用setTabAt(tab, i, fwd)表明当前index位置的链表正在扩容。

​ 以上就是扩容的具体逻辑。

10、remove(Object key, Object value)

public boolean remove(Object key, Object value) {
    if (key == null)
        throw new NullPointerException();
    return value != null && replaceNode(key, null, value) != null;
}
final V replaceNode(Object key, V value, Object cv) {
    int hash = spread(key.hashCode());
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0 ||
            (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            boolean validated = false;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    ......对f节点链表进行遍历搜索
                    }
                    else if (f instanceof TreeBin) {
                        ......
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (validated) {
                if (oldVal != null) {
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}

​ 这个就是其的获取逻辑。我们可以看到主要有两点:synchronized (f),还有就是如果正在进行扩容else if ((fh = f.hash) == MOVED),就会调用helpTransfer(tab, f);,先取帮助扩容。

​ 至此,关于ConcurrentHashMap的主要逻辑原理就梳理了。大体的内容还是明白了,但其中还有几个小点不是很能确认其的确切含义。