ConcurrentHashMap

152 阅读18分钟
  1. UNsafe

Unsafe类提供了硬件级别的原子操作,所以在一些并发编程中被大量使用。jdk已经作出说明,该类对程序员而言不是一个安全操作,在后续的jdk升级过程中,可能会禁用该类。

Unsafe Api

arrayBaseOffset:获取数组的基础偏移量

arrayIndexScale:获取数组中元素的偏移间隔,要获取对应所以的元素,将索引号和该值相乘,获得数组中指定角标元素的偏移量

getObjectVolatile:获取对象上的属性值或者数组中的元素

getObject:获取对象上的属性值或者数组中的元素,已过时

putOrderedObject:设置对象的属性值或者数组中某个角标的元素,更高效

putObjectVolatile:设置对象的属性值或者数组中某个角标的元素

putObject:设置对象的属性值或者数组中某个角标的元素,已过时

  1. 线程不安全HashMaphe 效率低下HashTable容器

使用1.7 Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

  1. JDK1.7

分离锁,也就是将内部进行分段(Segment),里面则是 HashEntry 的数组,和 HashMap 类似,哈希相同的条目也是以链表形式存放。HashEntry 内部使用 volatile 的 value 字段来保证可见性

具体实现可以理解为:ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。Segment是一种可重入锁(继承了ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构。一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元 素,每个Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时, 必须首先获得与它对应的Segment锁。

(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。

(4)ReentrantLock可选择是否为公平锁

初始化

在构造的时候,Segment 的数量由concurrentcyLevel 决定,默认是 16,理论上,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。也可以在相应构造函数直接指定。注意,Java 需要它是 2 的幂数值,如果输入是类似 15 这种非幂值,会被自动调整到 16 之类 2 的幂数值。并且一旦初始化后,它是不可以扩容的。

//空参构造

public ConcurrentHashMap() {

    //调用本类的带参构造

    //DEFAULT_INITIAL_CAPACITY = 16

    //DEFAULT_LOAD_FACTOR = 0.75f

    //int DEFAULT_CONCURRENCY_LEVEL = 16

    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);

}
//initialCapacity 定义ConcurrentHashMap存放元素的容量

//concurrencyLevel 定义ConcurrentHashMap中Segment[]的大小

public ConcurrentHashMap(int initialCapacity,

                         float loadFactor, int concurrencyLevel) {

   

    int sshift = 0;

    int ssize = 1;

    //计算Segment[]的大小,保证是2的幂次方数

    while (ssize < concurrencyLevel) {

        ++sshift;

        ssize <<= 1;

    }

    //这两个值用于后面计算Segment[]的角标

    this.segmentShift = 32 - sshift;4,28

    this.segmentMask = ssize - 1;15

    

    //计算每个Segment中存储元素的个数

    int c = initialCapacity / ssize;

    if (c * ssize < initialCapacity)

        ++c;

    //最小Segment中存储元素的个数为2

    int cap = MIN_SEGMENT_TABLE_CAPACITY;

    ////矫正每个Segment中存储元素的个数,保证是2的幂次方,最小为2

    while (cap < c)

        cap <<= 1;

    //创建一个Segment对象,作为其他Segment对象的模板

    Segment<K,V> s0 =

        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),

                         (HashEntry<K,V>[])new HashEntry[cap]);

    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];

    //利用Unsafe类,将创建的Segment对象存入0角标位置

    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]

    this.segments = ss;

}

ConcurrentHashMap中保存了一个默认长度为16的Segment[] ,每个Segment元素中保存了一个默认长度为2的HashEntry[] ,我们添加的元素,是存入对应的Segment中的HashEntry[]中。所以ConcurrentHashMap中默认元素的长度是32个,而不是16个

Segment

static final class Segment<K,V> extends ReentrantLock implements Serializable {

    ...

}

我们发现Segment是继承自ReentrantLock的,学过线程的兄弟都知道,它可以实现同步操作,从而保证多线程下的安全。因为每个Segment之间的锁互不影响,所以我们也将ConcurrentHashMap中的这种锁机制称之为分段锁,这比HashTable的线程安全操作高效的多。

HashEntry

//ConcurrentHashMap中真正存储数据的对象

static final class HashEntry<K,V> {

    final int hash; //通过运算,得到的键的hash值

    final K key; // 存入的键

    volatile V value; //存入的值

    volatile HashEntry<K,V> next; //记录下一个元素,形成单向链表



    HashEntry(int hash, K key, V value, HashEntry<K,V> next) {

        this.hash = hash;

        this.key = key;

        this.value = value;

        this.next = next;

    }

}

put

public V put(K key, V value) {

    Segment<K,V> s;

    if (value == null)

        throw new NullPointerException();

    //基于key,计算hash值

    int hash = hash(key);

    //因为一个键要计算两个数组的索引,为了避免冲突,这里取高位计算Segment[]的索引

    int j = (hash >>> segmentShift) & segmentMask;

    //判断该索引位的Segment对象是否创建,没有就创建

    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck

         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment

        s = ensureSegment(j);

    //调用Segmetn的put方法实现元素添加

    return s.put(key, hash, value, false);

}
//创建对应索引位的Segment对象,并返回

private Segment<K,V> ensureSegment(int k) {

    final Segment<K,V>[] ss = this.segments;

    long u = (k << SSHIFT) + SBASE; // raw offset

    Segment<K,V> seg;

    //获取,如果为null,即创建

    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {

        //以0角标位的Segment为模板

        Segment<K,V> proto = ss[0]; // use segment 0 as prototype

        int cap = proto.table.length;

        float lf = proto.loadFactor;

        int threshold = (int)(cap * lf);

        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];

        //获取,如果为null,即创建

        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))

            == null) { // recheck

            //创建

            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);

            //自旋方式,将创建的Segment对象放到Segment[]中,确保线程安全

            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))

                   == null) {

                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))

                    break;

            }

        }

    }

    //返回

    return seg;

}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {

    //尝试获取锁,获取成功,node为null,代码向下执行

    //如果有其他线程占据锁对象,那么去做别的事情,而不是一直等待,提升效率

    //scanAndLockForPut 稍后分析

    HashEntry<K,V> node = tryLock() ? null :

        scanAndLockForPut(key, hash, value);

    V oldValue;

    try {

        HashEntry<K,V>[] tab = table;

        //取hash的低位,计算HashEntry[]的索引

        int index = (tab.length - 1) & hash;

        //获取索引位的元素对象

        HashEntry<K,V> first = entryAt(tab, index);

        for (HashEntry<K,V> e = first;;) {

            //获取的元素对象不为空

            if (e != null) {

                K k;

                //如果是重复元素,覆盖原值

                if ((k = e.key) == key ||

                    (e.hash == hash && key.equals(k))) {

                    oldValue = e.value;

                    if (!onlyIfAbsent) {

                        e.value = value;

                        ++modCount;

                    }

                    break;

                }

                //如果不是重复元素,获取链表的下一个元素,继续循环遍历链表

                e = e.next;

            }

            else { //如果获取到的元素为空

                //当前添加的键值对的HashEntry对象已经创建

                if (node != null)

                    node.setNext(first); //头插法关联即可

                else

                    //创建当前添加的键值对的HashEntry对象

                    node = new HashEntry<K,V>(hash, key, value, first);

                //添加的元素数量递增

                int c = count + 1;

                //判断是否需要扩容

                if (c > threshold && tab.length < MAXIMUM_CAPACITY)

                    //需要扩容

                    rehash(node);

                else

                    //不需要扩容

                    //将当前添加的元素对象,存入数组角标位,完成头插法添加元素

                    setEntryAt(tab, index, node);

                ++modCount;

                count = c;

                oldValue = null;

                break;

            }

        }

    } finally {

        //释放锁

        unlock();

    }

    return oldValue;

}
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {

    //获取头部元素

    HashEntry<K,V> first = entryForHash(this, hash);

    HashEntry<K,V> e = first;

    HashEntry<K,V> node = null;

    int retries = -1; // negative while locating node

    while (!tryLock()) {

        //获取锁失败

        HashEntry<K,V> f; // to recheck first below

        if (retries < 0) {

            //没有下一个节点,并且也不是重复元素,创建HashEntry对象,不再遍历

            if (e == null) {

                if (node == null) // speculatively create node

                    node = new HashEntry<K,V>(hash, key, value, null);

                retries = 0;

            }

            else if (key.equals(e.key))

                //重复元素,不创建HashEntry对象,不再遍历

                retries = 0;

            else

                //继续遍历下一个节点

                e = e.next;

        }

        else if (++retries > MAX_SCAN_RETRIES) {

            //如果尝试获取锁的次数过多,直接阻塞

            //MAX_SCAN_RETRIES会根据可用cpu核数来确定

            lock();

            break;

        }

        else if ((retries & 1) == 0 &&

                 (f = entryForHash(this, hash)) != first) {

            //如果期间有别的线程获取锁,重新遍历

            e = first = f; // re-traverse if entry changed

            retries = -1;

        }

    }

    return node;

}

get

public V get(Object key) {

 Segment<K,V> s; // manually integrate access methods to reduce overhead

 HashEntry<K,V>[] tab;

 //计算hash值

 int h = hash(key);

 //同样的先定位到 key 所在的Segment ,然后从主内存中取出最新的节点

 long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;

 if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&

  (tab = s.table) != null) {

  //若Segment不为空,且链表也不为空,则遍历查找节点

  for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile

     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);

    e != null; e = e.next) {

   K k;

   //找到则返回它的 value 值,否则返回 null

   if ((k = e.key) == key || (e.hash == h && key.equals(k)))

    return e.value;

  }

 }

 return null;

}

扩容

private void rehash(HashEntry<K,V> node) {

    HashEntry<K,V>[] oldTable = table;

    int oldCapacity = oldTable.length;

    //两倍容量

    int newCapacity = oldCapacity << 1;

    threshold = (int)(newCapacity * loadFactor);

    //基于新容量,创建HashEntry数组

    HashEntry<K,V>[] newTable =

        (HashEntry<K,V>[]) new HashEntry[newCapacity];

    int sizeMask = newCapacity - 1;

           //实现数据迁移

    for (int i = 0; i < oldCapacity ; i++) {

        HashEntry<K,V> e = oldTable[i];

        if (e != null) {

            HashEntry<K,V> next = e.next;

            int idx = e.hash & sizeMask;

            if (next == null)   //  Single node on list

                //原位置只有一个元素,直接放到新数组即可

                newTable[idx] = e;

            else { // Reuse consecutive sequence at same slot

                //=========图一=====================

                HashEntry<K,V> lastRun = e;

                int lastIdx = idx;

                for (HashEntry<K,V> last = next;

                     last != null;

                     last = last.next) {

                    int k = last.hash & sizeMask;

                    if (k != lastIdx) {

                        lastIdx = k;

                        lastRun = last;

                    }

                }

                //=========图一=====================

                

                //=========图二=====================

                newTable[lastIdx] = lastRun;

                //=========图二=====================

                // Clone remaining nodes

                //=========图三=====================

                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {

                    V v = p.value;

                    int h = p.hash;

                    int k = h & sizeMask;

                    HashEntry<K,V> n = newTable[k];

                    //这里旧的HashEntry不会放到新数组

                    //而是基于原来的数据创建了一个新的HashEntry对象,放入新数组

                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);

                }

                //=========图三=====================

            }

        }

    }

    //采用头插法,将新元素加入到数组中

    int nodeIndex = node.hash & sizeMask; // add the new node

    node.setNext(newTable[nodeIndex]);

    newTable[nodeIndex] = node;

    table = newTable;

}

从头结点开始向后遍历,找到当前链表的最后几个下标相同的连续的节点

从lastRun节点到尾结点的这部分就可以整体迁移到新数组的对应下标位置了,因为它们的下标都是相同的,可以这样统一处理。

另外从头结点到 lastRun 之前的节点,无法统一处理,一个一个去复制。且注意,不是直接迁移,而是复制节点到新的数组,旧的节点会在不久的将来,因为没有引用指向,JVM 垃圾回收处理掉。

size

public int size() {

    // Try a few times to get accurate count. On failure due to

    // continuous async changes in table, resort to locking.

    final Segment<K,V>[] segments = this.segments;

    int size;

    boolean overflow; // true if size overflows 32 bits

    long sum;         // sum of modCounts

    long last = 0L;   // previous sum

    int retries = -1; // first iteration isn't retry

    try {

        for (;;) {

            //超过次数会将整个Segment[]的所有Segment对象锁住

            if (retries++ == RETRIES_BEFORE_LOCK) {

                for (int j = 0; j < segments.length; ++j)

                    ensureSegment(j).lock(); // force creation

            }

            sum = 0L;

            size = 0;

            overflow = false;

            for (int j = 0; j < segments.length; ++j) {

                Segment<K,V> seg = segmentAt(segments, j);

                if (seg != null) {

                    //累加所有Segment的操作次数

                    sum += seg.modCount;

                    int c = seg.count;

                    //累加所有segment中的元素个数 size+=c

                    if (c < 0 || (size += c) < 0)

                        overflow = true;

                }

            }

            //当这次累加值和上一次累加值一样,证明没有进行新的增删改操作,返回sum

            //第一次last为0,如果有元素的话,这个for循环最少循环两次的

            if (sum == last)

                break;

            //记录累加的值

            last = sum;

        }

    } finally {

        //如果之前有锁住,解锁

        if (retries > RETRIES_BEFORE_LOCK) {

            for (int j = 0; j < segments.length; ++j)

                segmentAt(segments, j).unlock();

        }

    }

    //溢出,返回int的最大值,否则返回累加的size

    return overflow ? Integer.MAX_VALUE : size;

}

4.JDK1.8

容器初始化

源码分析

在jdk8的ConcurrentHashMap中一共有5个构造方法,这四个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做了处理,数组初始化是在第一次添加元素时完成

//没有维护任何变量的操作,如果调用该方法,数组长度默认是16

public ConcurrentHashMap() {

}


public ConcurrentHashMap(int initialCapacity) {

this(initialCapacity, LOAD_FACTOR, 1);

}
//调用两个参数的构造

public ConcurrentHashMap(int initialCapacity, float loadFactor) {

    this(initialCapacity, loadFactor, 1);

}
//计算一个大于或者等于给定的容量值,该值是2的幂次方数作为初始容量

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;

}
//基于一个Map集合,构建一个ConcurrentHashMap

//初始容量为16

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {

    this.sizeCtl = DEFAULT_CAPACITY;

    putAll(m);

}

sizeCtl含义解释

sizeCtl为0,代表数组未初始化, 且数组的初始容量为16

sizeCtl为正数,如果数组未初始化,那么其记录的是数组的初始容量,如果数组已经初始化,那么其记录的是数组的扩容阈值

sizeCtl为-1,表示数组正在进行初始化

sizeCtl小于0,并且不是-1,表示数组正在扩容, -(1+n),表示此时有n个线程正在共同完成数组的扩容操作

put/putVal方法

public V put(K key, V value) {

    return putVal(key, value, false);

}
final V putVal(K key, V value, boolean onlyIfAbsent) {

    //如果有空值或者空键,直接抛异常

    if (key == null || value == null) throw new NullPointerException();

    //基于key计算hash值,并进行一定的扰动

    int hash = spread(key.hashCode());

    //记录某个桶上元素的个数,如果超过8个,会转成红黑树

    int binCount = 0;

    for (Node<K,V>[] tab = table;;) {

        Node<K,V> f; int n, i, fh;

        //如果数组还未初始化,先对数组进行初始化

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

            tab = initTable();

            //如果hash计算得到的桶位置没有元素,利用cas将元素添加

        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

            //cas+自旋(和外侧的for构成自旋循环),保证元素添加安全

            if (casTabAt(tab, i, null,

                         new Node<K,V>(hash, key, value, null)))

                break;                   // no lock when adding to empty bin

        }

        //如果hash计算得到的桶位置元素的hash值为MOVED,证明正在扩容,那么协助扩容

        else if ((fh = f.hash) == MOVED)

            tab = helpTransfer(tab, f);

        else {

            //hash计算的桶位置元素不为空,且当前没有处于扩容操作,进行元素添加

            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, null);

                                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;

                        }

                    }

                }

            }

            if (binCount != 0) {

                //链表长度大于/等于8,将链表转成红黑树

                if (binCount >= TREEIFY_THRESHOLD)

                    treeifyBin(tab, i);

                //如果是重复键,直接将旧值返回

                if (oldVal != null)

                    return oldVal;

                break;

            }

        }

    }

    //添加的是新元素,维护集合长度,并判断是否要进行扩容操作

    addCount(1L, binCount);

    return null;

}

数组初始化,initTable方法

private final Node<K,V>[] initTable() {

    Node<K,V>[] tab; int sc;

    //cas+自旋,保证线程安全,对数组进行初始化操作

    while ((tab = table) == null || tab.length == 0) {

        //如果sizeCtl的值(-1)小于0,说明此时正在初始化, 让出cpu

        if ((sc = sizeCtl) < 0)

            Thread.yield(); // lost initialization race; just spin

        //cas修改sizeCtl的值为-1,修改成功,进行数组初始化,失败,继续自旋

        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {

            try {

                if ((tab = table) == null || tab.length == 0) {

                    //sizeCtl为0,取默认长度16,否则去sizeCtl的值

                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;

                    @SuppressWarnings("unchecked")

                    //基于初始长度,构建数组对象

                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];

                    table = tab = nt;

                    //计算扩容阈值,并赋值给sc

                    sc = n - (n >>> 2);

                }

            } finally {

                //将扩容阈值,赋值给sizeCtl

                sizeCtl = sc;

            }

            break;

        }

    }

    return tab;

}

扩容安全

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {

 int n = tab.length, stride;

 //根据当前CPU核心数,确定每次推进的步长,最小值为16.(为了方便我们以2为例)

 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)

  stride = MIN_TRANSFER_STRIDE; // subdivide range

 //从 addCount 方法,只会有一个线程跳转到这里,初始化新数组

 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 指代新数组

  nextTable = nextTab;

  //这里就把推进的下标值初始化为原数组长度(以16为例)

  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

 //i 代表当前线程正在迁移的桶的下标,bound代表它本次可以迁移的范围下限

 for (int i = 0, bound = 0;;) {

  Node<K,V> f; int fh;

  //需要向前推进

  while (advance) {

   int nextIndex, nextBound;

   // (1) 先看 (3) 。i每次自减 1,直到 bound。若超过bound范围,或者finishing标志为true,则不用向前推进。

   //若未全部完成迁移,且 i 并未走到 bound,则跳转到 (7),处理当前桶的元素迁移。

   if (--i >= bound || finishing)

    advance = false;

   // (2) 每次执行,都会把 transferIndex 最新的值同步给 nextIndex

   //若 transferIndex小于等于0,则说明原数组中的每个桶位置,都有线程在处理迁移了,

   //于是,需要跳出while循环,并把 i设为 -1,以跳转到④判断在处理的线程是否已经全部完成。

   else if ((nextIndex = transferIndex) <= 0) {

    i = -1;

    advance = false;

   }

   // (3) 第一个线程会先走到这里,确定它的数据迁移范围。(2)处会更新 nextIndex为 transferIndex 的最新值

   //因此第一次 nextIndex=n=16,nextBound代表当次迁移的数据范围下限,减去步长即可,

   //所以,第一次时,nextIndex=16,nextBound=16-2=14。后续,每次都会间隔一个步长。

   else if (U.compareAndSwapInt

      (this, TRANSFERINDEX, nextIndex,

       nextBound = (nextIndex > stride ?

           nextIndex - stride : 0))) {

    //bound代表当次数据迁移下限

    bound = nextBound;

    //第一次的i为15,因为长度16的数组,最后一个元素的下标为15

    i = nextIndex - 1;

    //表明不需要向前推进,只有当把当前范围内的数据全部迁移完成后,才可以向前推进

    advance = false;

   }

  }

  // (4)

  if (i < 0 || i >= n || i + n >= nextn) {

   int sc;

   //若全部线程迁移完成

   if (finishing) {

    nextTable = null;

    //更新table为新表

    table = nextTab;

    //扩容阈值改为原来数组长度的 3/2 ,即新长度的 3/4,也就是新数组长度的0.75倍

    sizeCtl = (n << 1) - (n >>> 1);

    return;

   }

   //到这,说明当前线程已经完成了自己的所有迁移(无论参与了几次迁移),

   //则把 sc 减1,表明参与扩容的线程数减少 1。

   if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {

    //在 addCount 方法最后,我们强调,迁移开始时,会设置 sc=(rs << RESIZE_STAMP_SHIFT) + 2

    //每当有一个线程参与迁移,sc 就会加 1,每当有一个线程完成迁移,sc 就会减 1。

    //因此,这里就是去校验当前 sc 是否和初始值是否相等。相等,则说明全部线程迁移完成。

    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)

     return;

    //只有此处,才会把finishing 设置为true。

    finishing = advance = true;

    //这里非常有意思,会把 i 从 -1 修改为16,

    //目的就是,让 i 再从后向前扫描一遍数组,检查是否所有的桶都已被迁移完成,参看 (6)

    i = n; // recheck before commit

   }

  }

  // (5) 若i的位置元素为空,则说明当前桶的元素已经被迁移完成,就把头结点设置为fwd标志。

  else if ((f = tabAt(tab, i)) == null)

   advance = casTabAt(tab, i, null, fwd);

  // (6) 若当前桶的头结点是 ForwardingNode ,说明迁移完成,则向前推进 

  else if ((fh = f.hash) == MOVED)

   advance = true; // already processed

  //(7) 处理当前桶的数据迁移。

  else {

   synchronized (f) {  //给头结点加锁

    if (tabAt(tab, i) == f) {

     Node<K,V> ln, hn;

     //若hash值大于等于0,则说明是普通链表节点

     if (fh >= 0) {

      int runBit = fh & n;

      //这里是 1.7 的 CHM 的 rehash 方法和 1.8 HashMap的 resize 方法的结合体。

      //会分成两条链表,一条链表和原来的下标相同,另一条链表是原来的下标加数组长度的位置

      //然后找到 lastRun 节点,从它到尾结点整体迁移。

      //lastRun前边的节点则单个迁移,但是需要注意的是,这里是头插法。

      //另外还有一点和1.7不同,1.7 lastRun前边的节点是复制过去的,而这里是直接迁移的,没有复制操作。

      //所以,最后会有两条链表,一条链表从 lastRun到尾结点是正序的,而lastRun之前的元素是倒序的,

      //另外一条链表,从头结点开始就是倒叙的。看下图。

      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;

     }

    }

   }

  }

 }

}

多线程扩容效率改进

多线程协助扩容的操作会在两个地方被触发:

① 当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素

② 当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizeCtl的值小于0,并且新数组不为空,这个时候,会去协助扩容

//发现此处为fwd节点,协助扩容,扩容结束后,再循环回来添加元素

        else if ((fh = f.hash) == MOVED)

            tab = helpTransfer(tab, f);
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {

 Node<K,V>[] nextTab; int sc;

 //头结点为 ForwardingNode ,并且新数组已经初始化

 if (tab != null && (f instanceof ForwardingNode) &&

  (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {

  int rs = resizeStamp(tab.length);

  while (nextTab == nextTable && table == tab &&

      (sc = sizeCtl) < 0) {

   //若校验标识失败,或者已经扩容完成,或推进下标到头,则退出

   if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

    sc == rs + MAX_RESIZERS || transferIndex <= 0)

    break;

   //当前线程需要帮助迁移,sc值加1

   if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {

    transfer(tab, nextTab);

    break;

   }

  }

  return nextTab;

 }

 return table;

}
private final void addCount(long x, int check) {

    //省略代码

    

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

            //sizeCtl小于0,说明正在执行扩容,那么协助扩容

            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();

        }

    }

}

集合长度的累计方式

1.1、addCount方法

① CounterCell数组不为空,优先利用数组中的CounterCell记录数量

② 如果数组为空,尝试对baseCount进行累加,失败后,会执行fullAddCount逻辑

③ 如果是添加元素操作,会继续判断是否需要扩容

private final void addCount(long x, int check) {

    CounterCell[] as; long b, s;

    //当CounterCell数组不为空,则优先利用数组中的CounterCell记录数量

    //或者当baseCount的累加操作失败,会利用数组中的CounterCell记录数量

    if ((as = counterCells) != null ||

        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {

        CounterCell a; long v; int m;

        //标识是否有多线程竞争

        boolean uncontended = true;

        //当as数组为空

        //或者当as长度为0

        //或者当前线程对应的as数组桶位的元素为空

        //或者当前线程对应的as数组桶位不为空,但是累加失败

        if (as == null || (m = as.length - 1) < 0 ||

            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||

            !(uncontended =

              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

            //以上任何一种情况成立,都会进入该方法,传入的uncontended是false

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

            //sc小于0,说明有线程正在扩容,那么会协助扩容

            if (sc < 0) {

                //扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移,结束操作

                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||

                    transferIndex <= 0)

                    break;

                //扩容线程加1,成功后,进行协助扩容操作

                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))

                    //协助扩容,newTable不为null

                    transfer(tab, nt);

            }

            //没有其他线程在进行扩容,达到扩容阈值后,给sizeCtl赋了一个很大的负数

            //1+1=2 --》 代表此时有一个线程在扩容

            

            //rs << RESIZE_STAMP_SHIFT)是一个很大的负数

            else if (U.compareAndSwapInt(this, SIZECTL, sc,

                                         (rs << RESIZE_STAMP_SHIFT) + 2))

                //扩容,newTable为null

                transfer(tab, null);

            s = sumCount();

        }

    }

}
1.2、fullAddCount方法

① 当CounterCell数组不为空,优先对CounterCell数组中的CounterCell的value累加

② 当CounterCell数组为空,会去创建CounterCell数组,默认长度为2,并对数组中的CounterCell的value累加

③ 当数组为空,并且此时有别的线程正在创建数组,那么尝试对baseCount做累加,成功即返回,否则自旋

private final void fullAddCount(long x, boolean wasUncontended) {

    int h;

    //获取当前线程的hash值

    if ((h = ThreadLocalRandom.getProbe()) == 0) {

        ThreadLocalRandom.localInit();      // force initialization

        h = ThreadLocalRandom.getProbe();

        wasUncontended = true;

    }

    //标识是否有冲突,如果最后一个桶不是null,那么为true

    boolean collide = false;                // True if last slot nonempty

    for (;;) {

        CounterCell[] as; CounterCell a; int n; long v;

        //数组不为空,优先对数组中CouterCell的value累加

        if ((as = counterCells) != null && (n = as.length) > 0) {

            //线程对应的桶位为null

            if ((a = as[(n - 1) & h]) == null) {

                if (cellsBusy == 0) {            // Try to attach new Cell

                    //创建CounterCell对象

                    CounterCell r = new CounterCell(x); // Optimistic create

                    //利用CAS修改cellBusy状态为1,成功则将刚才创建的CounterCell对象放入数组中

                    if (cellsBusy == 0 &&

                        U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {

                        boolean created = false;

                        try {               // Recheck under lock

                            CounterCell[] rs; int m, j;

                            //桶位为空, 将CounterCell对象放入数组

                            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;

                        //桶位已经被别的线程放置了已给CounterCell对象,继续循环

                        continue;           // Slot is now non-empty

                    }

                }

                collide = false;

            }

            //桶位不为空,重新计算线程hash值,然后继续循环

            else if (!wasUncontended)       // CAS already known to fail

                wasUncontended = true;      // Continue after rehash

            //重新计算了hash值后,对应的桶位依然不为空,对value累加

            //成功则结束循环

            //失败则继续下面判断

            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))

                break;

            //数组被别的线程改变了,或者数组长度超过了可用cpu大小,重新计算线程hash值,否则继续下一个判断

            else if (counterCells != as || n >= NCPU)

                collide = false;            // At max size or stale

            //当没有冲突,修改为有冲突,并重新计算线程hash,继续循环

            else if (!collide)

                collide = true;

            //如果CounterCell的数组长度没有超过cpu核数,对数组进行两倍扩容

            //并继续循环

            else if (cellsBusy == 0 &&

                     U.compareAndSwapInt(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);

        }

        //CounterCell数组为空,并且没有线程在创建数组,修改标记,并创建数组

        else if (cellsBusy == 0 && counterCells == as &&

                 U.compareAndSwapInt(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;

        }

        //数组为空,并且有别的线程在创建数组,那么尝试对baseCount做累加,成功就退出循环,失败就继续循环

        else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))

            break;                          // Fall back on using base

    }

}

长度获取

1.1、size方法
public int size() {

    long n = sumCount();

    return ((n < 0L) ? 0 :

            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :

            (int)n);

}
1.2、sumCount方法
final long sumCount() {

    CounterCell[] as = counterCells; CounterCell a;

    //获取baseCount的值

    long sum = baseCount;

    if (as != null) {

        //遍历CounterCell数组,累加每一个CounterCell的value值

        for (int i = 0; i < as.length; ++i) {

            if ((a = as[i]) != null)

                sum += a.value;

        }

    }

    return sum;

}

注意:这个方法并不是线程安全的