Java-并发-容器-ConcurrentHashMap

Java-并发-容器-ConcurrentHashMap

0x01 摘要

本文讲讲Java中使用率极高的线程安全类ConcurrentHashMap

会提一些java7 和 java8中的不同之处

一些重要方法画了流程图便于理解

0x02 重要概念

2.1 数据结构

2.1.1 Java7

java 7中有一个segment数组,每个元素是一个segment,在其之上又是一个Entry数组,这个数组的每个元素才是个链表。
Java7ConcurrentHashMap数据结构

Java7ConcurrentHashMap数据结构2

2.1.2 Java8

java 8放弃了segment机制,而是每个数组成员就是一个桶,可以放链表或红黑树。下图中TreeBin代表红黑树节点,Node表示普通链表节点。
Java8ConcurrentHashMap数据结构

2.1.3 红黑树

红黑树
一棵红黑树是满足以下性质的二叉搜索树:

  1. 每个节点是红、黑两色之一
  2. 根节点一定是黑色
  3. 叶子节点(是指为空(NIL或NULL)的叶子节点)一定是黑色
  4. 如果一个节点是红色,那么它的两个子节点都一定是黑色
  5. 对于每个非叶子节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

这里还有两个概念:

  • 哨兵节点,为了方便处理边界条件以及方便描述,我们把不需要关注的叶子节点和null节点统一用一个特殊节点表示,我们称它为哨兵节点,记为T.NIL。
  • 黑高,是从某个节点x出发(不含该节点)达到一个叶节点的任意一条简单路径上的黑色节点个数称为该节点的黑高。

关于红黑树的更多内容,可以查看这篇文章:数据结构-常用树总结

2.2 线程安全

2.2.1 synchronized
  • java 7在放入元素时,在segment级别用了synchronized锁。也就是说能支持segment个线程并发。但有个问题就是遍历链表的效率是O(N),在元素过多的时候效率就很低下了。
  • java 8在放入元素时,在单个数组元素(即桶)的头节点用了synchronized锁。也就是说在java8中竞争锁定几率比起java7大大降低了。此外,当链表个数达到8时,会将链表转换成红黑树,使得查询效率变为O(logN)

java8中,ConcurrentHashMap在以下场景使用了synchronized锁:

  • treeifyBin
  • 在每个bin头结点做插入、删除、修改数据操作时
  • 对节点扩容转移复制时
2.2.2 CAS

除了使用效率不高的synchronized锁,ConcurrentHashMap还大量使用CAS方式的乐观锁,比如:

  • initTable
  • transfer
    以上两个都是以类似以下代码获取乐观锁,此方法返回true就代表获取到了乐观锁,就能进行独占操作。
UNSAFE.compareAndSwapInt(this, SIZECTL, sc, -1))

再比如这段创建bin的链表头结点的代码:

if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))
   break; 

获得在i位置上的Node:

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

0x03 源码解析

3.1 重要定义

3.1.1 类定义
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable 

ConcurrentHashMap继承关系

3.1.2 关于sizeCtl

sizeCtlConcurrentHashMap中有特殊含义:

阶段含义
默认0默认值
只指定了容量,尚未真正创建数组正整数表示初始容量
初始化开始,但尚未结束-1正在初始化
初始化结束,创建数组完成正整数依赖这个值进行扩容,大约为容量的四分之三
首个线程扩容(resizeStamp(tab.length) << RESIZE_STAMP_SHIFT) + 2该值为一个绝对值很大的负数,代表首个线程扩容
正在扩容N = N + 1其他某个线程辅助扩容开始
结束扩容N = N - 1其他某个线程辅助扩容结束

sizeCtl

3.1.3 关于Node.hash值

.

经过spread以及一定的规则计算后得到的最终hash值在ConcurrentHashMap中有特殊含义:

名称含义
-1MOVEDforwarding nodes
-2TREEBIN红黑树根节点
-3RESERVEDhash for transient reservations
非负数-普通节点

3.2 重要属性和初始化方法

3.2.1 常量
// 最大容量是2的30次幂,还剩两位用来作为控制位
private static final int MAXIMUM_CAPACITY = 1 << 30;

// 默认的初始容量值,必须是2的次方,最小为1
// 这样的规定我们已经很熟悉了,无非是为了方便扩容和查找元素位置时直接用bit操作,效率高
// 比如16二进制为10000 indexOf一般是用hashcode&(10000-1)=hashcode&1111
// 这个我们就能发现,这个结果是均匀的,其他数字都没有这个效果
private static final int DEFAULT_CAPACITY = 16;

// 可能达到的最大的数组大小值(非2的次幂),2的31次方-8,被toArray及相关的方法使用
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 无效参数,旧版本兼容就继续放这里了 
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

// 默认负载因子,用来衡量是否应该扩容
// 构造方法里指定的负载因子只会影响初始化的table容量
// 通常不使用实际浮点值 - 
// 而是使用诸如 n -(n >>> 2) 之类的表达式来简化相关的大小调整阈值。
private static final float LOAD_FACTOR = 0.75f;

/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2, and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 */
// 某个Entry数组节点上Entry链表升级为红黑树的阈值。
static final int TREEIFY_THRESHOLD = 8;

/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 */
// 用于在调整大小期间拆解红黑树(拆分)阈值。 
// 应该小于TREEIFY_THRESHOLD,
static final int UNTREEIFY_THRESHOLD = 6;

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * The value should be at least 4 * TREEIFY_THRESHOLD to avoid
 * conflicts between resizing and treeification thresholds.
 * 容器可以转为红黑树的最小数组容量。 (否则,如果bin中的节点太多,则会调整表的大小。)
 * 该值应至少为4 * TREEIFY_THRESHOLD,以避免扩容和树化阈值之间的冲突。
 * 
 */ 
static final int MIN_TREEIFY_CAPACITY = 64;

/**
 * Minimum number of rebinnings per transfer step. Ranges are
 * subdivided to allow multiple resizer threads.  This value
 * serves as a lower bound to avoid resizers encountering
 * excessive memory contention.  The value should be at least
 * DEFAULT_CAPACITY.
 * 每次转移步骤的最小重组次数。 
 * 范围细分为允许多个缩放器线程。 
 * 此值用作下限以避免扩容线程遇到过多的内存竞争。 
 * 该值至少应为DEFAULT_CAPACITY(16)。
 */
private static final int MIN_TRANSFER_STRIDE = 16;

// sizeCtl中用来生成时代戳的位数
// 如果是32bit的数组,则该值必须至少是6
private static int RESIZE_STAMP_BITS = 16;

/**
 * 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;

// sizeCtl中用来记录大小戳的bit位移。
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

// 特殊的Node hash值 
static final int MOVED     = -1; // forwarding nodes
static final int TREEBIN   = -2; // 红黑树根节点
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // 用在普通node的hash计算中

/** Number of CPUS, to place bounds on some sizings */
// CPU数量,用来限制某些边界情况
static final int NCPU = Runtime.getRuntime().availableProcessors();

// 序列化兼容性
private static final ObjectStreamField[] serialPersistentFields = {
    new ObjectStreamField("segments", Segment[].class),
    new ObjectStreamField("segmentMask", Integer.TYPE),
    new ObjectStreamField("segmentShift", Integer.TYPE)
};
3.2.2 Fields
// Node数组。在第一次插入后才懒初始化
// 大小永远是2的N次幂
// iterator直接可以访问遍历
// 注意这里虽然用了volatile保证table对多线程可见性,但其内部元素却没有可见性保证
// 所以我们获取某个位置的Node的时候使用的是类似 
// (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
transient volatile Node<K,V>[] table;

// 用来在扩容时使用的Node数组,只有扩容时才不是null
private transient volatile Node<K,V>[] nextTable;

/**
 * Base counter value, used mainly when there is no contention,
 * but also as a fallback during table initialization
 * races. Updated via CAS.
 * 这个暂时不知道啥用处,等会儿再看
 */
private transient volatile long baseCount;

/**
 * Table initialization and resizing control.  When negative, the
 * table is being initialized or resized: -1 for initialization,
 * else -(1 + the number of active resizing threads).  Otherwise,
 * when table is null, holds the initial table size to use upon
 * creation, or 0 for default. After initialization, holds the
 * next element count value upon which to resize the table.
 * 用来控制table初始化和扩容,不同的值含义不同:
 * 1. 负数:数组正在被初始化或扩容:
 * 	1.1 -1是初始化
 *     1.2 否则是比-1还小的一个负数,符号后面的整数代表辅助扩容的线程数
 * 2. 非负数
 * 	2.1 当数组不为空时,表示初始创建时的大小,或0表示默认值
 *     2.2 初始化完成后,holds the next element count value ,依赖这个值扩容
 */
private transient volatile int sizeCtl;

/**
 * The next table index (plus one) to split while resizing.
 */
// 扩容时新的数组下标(加了1)
private transient volatile int transferIndex;

/**
 * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
 */
// 通过CAS锁定的自旋锁,当扩容和创建时使用
private transient volatile int cellsBusy;

// counter cells数组. 非空时大小是2的N次幂
private transient volatile CounterCell[] counterCells;

// key value Entry集合的视图
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
3.2.3 static块
// 又在用Unsafe搞一些操作
static {
    try {
        U = sun.misc.Unsafe.getUnsafe();
        Class<?> k = ConcurrentHashMap.class;
        // 获取各重要变量相对于对象的偏移量
        SIZECTL = U.objectFieldOffset
            (k.getDeclaredField("sizeCtl"));
        TRANSFERINDEX = U.objectFieldOffset
            (k.getDeclaredField("transferIndex"));
        BASECOUNT = U.objectFieldOffset
            (k.getDeclaredField("baseCount"));
        CELLSBUSY = U.objectFieldOffset
            (k.getDeclaredField("cellsBusy"));
        Class<?> ck = CounterCell.class;
        CELLVALUE = U.objectFieldOffset
            (ck.getDeclaredField("value"));
        Class<?> ak = Node[].class;
        ABASE = U.arrayBaseOffset(ak);
        int scale = U.arrayIndexScale(ak);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
    } catch (Exception e) {
        throw new Error(e);
    }
}

3.3 内部类

3.3.1 Node
3.3.1.1 Node-普通节点

Node是最核心的内部类,它继承了Map.Entry,存储了key, value, hash以及指向下一个Node的next指针。

ConcurrentHashMapHashMap中的Node的定义很相似,但有一些差别即它对value和next属性设置了volatile(与JDK7的Segment相同)。它不允许调用setValue方法直接改变Node的value域,会抛出UnsupportedOperationException。它增加了find方法辅助map.get()方法通过key和hash值查找对应的Node。

/**
 * Key-value entry.  This class is never exported out as a
 * user-mutable Map.Entry (i.e., one supporting setValue; see
 * MapEntry below), but can be used for read-only traversals used
 * in bulk tasks.  Subclasses of Node with a negative hash field
 * are special, and contain null keys and values (but are never
 * exported).  Otherwise, keys and vals are never null.
 * Key-value的Entry
 * 此类并不会以用户可改变的Map.Entry对外暴露,但可以被用来在批量任务重做只读的遍历
 * Node类还拥有几个子类,其中hash值为负数的是特殊类,并且包含空键和值(但永远不会d对外暴露)。
 * 此外,key和value不能为空
 */
static class Node<K,V> implements Map.Entry<K,V> {
    // hash用final修饰,不可变
    final int hash;
    // key用final修饰,不可变
    final K key;
    // 注意val和next用了volatile修饰,保证最新值对其他线程可见性
    volatile V val;
    volatile Node<K,V> next;

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

    public final K getKey()       { return key; }
    public final V getValue()     { return val; }
    public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
    public final String toString(){ return key + "=" + val; }
    // 注意这里,不支持通过setValue改变val
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }
    // 判断两个Entry是否相等,需要同时满足以下5个条件:
    // 1.目标对象属于Map.Entry
    // 2.目标key和不为空
    // 3.目标value不为空
    // 4.目标key和本节点key指向相同对象或equals返回true
    // 5.目标value和本节点val指向相同对象或equals返回true
    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)) &&
                // 这里用临时变量u的意义是不再有volatile语义
                // 使得v == (u = val)和v.equals(u)中的u是同一个对象
                // 如果直接用val来比,就必须每次都去主内存读最新值
                // 也就是说这两个判别式中val的值都变了
                (v == (u = val) || v.equals(u)));
    }

    /**
     * Virtualized support for map.get(); 
     * 子类中都有重写该方法
     * 通过制定的hash值h和key k来从当前Node开始沿链表正向寻找匹配的Node
     */
    Node<K,V> find(int h, Object k) {
        Node<K,V> e = this;
        if (k != null) {
            do {
                K ek;
                // 比较规则是hash值相等
                // 且key指向同一对象或equals返回true
                if (e.hash == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
            } while ((e = e.next) != null);
        }
        return null;
    }
}

Node看完之后,建议先阅读其他章节内容,最后再回来看以下几个子类方便理解。

3.3.1.2 ForwardingNode-正在扩容节点

ForwardingNode用于连接两个table的节点类,表示扩容。

内部的nextTable指针指向扩容后的数组。

这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。

/**
 * 这个Node用来在扩容转移数据的时候插入到bins的头部
 */
static final class ForwardingNode<K,V> extends Node<K,V> {
    // 指向扩容后的新数组
    final Node<K,V>[] nextTable;
    // 构造方法,参数是新的Entry数组
    ForwardingNode(Node<K,V>[] tab) {
        // 创建一个Node,hash设为MOVED(-1),其他为0,表示我是一个扩容节点
        super(MOVED, null, null, null);
        // nextTable指向该新数组
        this.nextTable = tab;
    }
    // 注意find方法是从新的数组nextTable中查找
    Node<K,V> find(int h, Object k) {
        // 循环以避免ForwardingNode上的反复地深度递归
        outer: for (Node<K,V>[] tab = nextTable;;) {
            Node<K,V> e; int n;
            if (k == null || tab == null || (n = tab.length) == 0 ||
                (e = tabAt(tab, (n - 1) & h)) == null)
                return null;
            for (;;) {
                int eh; K ek;
                if ((eh = e.hash) == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
                if (eh < 0) {
                    if (e instanceof ForwardingNode) {
                        tab = ((ForwardingNode<K,V>)e).nextTable;
                        continue outer;
                    }
                    else
                        return e.find(h, k);
                }
                if ((e = e.next) == null)
                    return null;
            }
        }
    }
}
3.3.1.3 ReservationNode
/**
 * A place-holder node used in computeIfAbsent and compute
 */
static final class ReservationNode<K,V> extends Node<K,V> {
    ReservationNode() {
        super(RESERVED, null, null, null);
    }

    Node<K,V> find(int h, Object k) {
        return null;
    }
}
3.3.1.4 TreeNode-红黑树节点

TreeNode是红黑树节点类。

当Entry数组的某个成员链表长度达到8的时候,会将链表转换为TreeNode。但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap继承自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。

只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或已经被复制到新数组。

/**
 * 代表红黑树节点
 * Nodes for use in TreeBins
 */
static final class TreeNode<K,V> extends Node<K,V> {
    // 红黑树父节点
    TreeNode<K,V> parent; 
    // 红黑树左孩子节点
    TreeNode<K,V> left;
    // 红黑树右孩子节点
    TreeNode<K,V> right;
    // 需要在删除后取消链接
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        // 调用父类Node的构造方法     
        super(hash, key, val, next);
        // 保存父节点引用
        this.parent = parent;
    }
	 // 通过hash和key搜索Node
    Node<K,V> find(int h, Object k) {
        return findTreeNode(h, k, null);
    }

    /**
     * Returns the TreeNode (or null if not found) for the given key
     * starting at given root.
     * 当搜索有结果时,返回TreeNode
     * 否则返回null
     */
    final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
        if (k != null) {
            TreeNode<K,V> p = this;
            // 遍历查找
            do  {
                int ph, dir; K pk; TreeNode<K,V> q;
                // p的左右子节点
                TreeNode<K,V> pl = p.left, pr = p.right;
                if ((ph = p.hash) > h)
                // 如果当前节点p的hash值大于参数中的hash,就将p指向左子节点继续查找
                    p = pl;
                else if (ph < h)
                // 如果当前节点p的hash值小于参数中的hash,就将p指向右子节点继续查找
                    p = pr;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                // 此时当前节点p的hash值等于参数中的hash,就比较他们的key是否相同或相等
                    return p;
                else if (pl == null)
                // 没有找到匹配的节点且左子节点为null,就指向右子节点继续找
                    p = pr;
                else if (pr == null)
                // 没有找到匹配的节点且右子节点为null,就指向左子节点继续找
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                else if ((q = pr.findTreeNode(h, k, kc)) != null)
                // 没有找到匹配的节点且左右子节点都不为null,就从右子节点查找
                    return q;
                else
                // 右子节点查找完毕无结果,找左子节点
                    p = pl;
            } while (p != null);
        }
        return null;
    }
}
3.3.1.5 TreeBin-红黑树根节点

这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。

注意放入红黑树的时候的规则,主要是比较hash值:

  • 如果放入的节点x的hash值比当前节点xp小,就把放入节点作为当前节点的左孩子(xp.left = x)
  • 否则作为右孩子(xp.right = x)
    在此之后还要把放入节点的parent指针指向当前节点(x.parent = xp)
    每次插入一个节点后需要需要通过调用balanceInsertion(root, x)方法调整树以符合红黑树规则。
/**
 * TreeNodes used at the heads of bins. TreeBins do not hold user
 * keys or values, but instead point to list of TreeNodes and
 * their root. They also maintain a parasitic read-write lock
 * forcing writers (who hold bin lock) to wait for readers (who do
 * not) to complete before tree restructuring operations.
 * 在Entry数组的每个位置上的bin存的是这个TreeBin而不是TreeNode。
 * TreeBin内封装了一个头结点为root的TreeNode的红黑树,含有若干TreeNode。
 * TreeBin并不持有key和value。
 * 
 * 它们还保持读写锁,迫使持有bin锁的写入者等待没有锁的读者在树重组操作之前完成。
 */
static final class TreeBin<K,V> extends Node<K,V> {
     // 存储红黑树的根节点TreeNode
    TreeNode<K,V> root;
    // 虽然为红黑树,但还维护了一个链表结构,以便在untreeify时使用。first指向链表的头部。
    volatile TreeNode<K,V> first;
    volatile Thread waiter;
    volatile int lockState;
    // values for lockState
    static final int WRITER = 1; // set while holding write lock
    static final int WAITER = 2; // set when waiting for write lock
    static final int READER = 4; // increment value for setting read lock

    /**
     * Tie-breaking utility for ordering insertions when equal
     * hashCodes and non-comparable. We don't require a total
     * order, just a consistent insertion rule to maintain
     * equivalence across rebalancings. Tie-breaking further than
     * necessary simplifies testing a bit.
     */
    static int tieBreakOrder(Object a, Object b) {
        int d;
        if (a == null || b == null ||
            (d = a.getClass().getName().
             compareTo(b.getClass().getName())) == 0)
            d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
                 -1 : 1);
        return d;
    }

    /**
     * 通过根节点b TreeNode来构建一个TreeBin
     */
    TreeBin(TreeNode<K,V> b) {
        // 注意这里,只有hash为TREEBIN,其他都为空
        // 说明TreeBin本身是不保存任何用户数据的
        super(TREEBIN, null, null, null);
        this.first = b;
        // 存放根节点
        TreeNode<K,V> r = null;
        for (TreeNode<K,V> x = b, next; x != null; x = next) {
            // next指向当前元素x的next元素
            next = (TreeNode<K,V>)x.next;
            // 初始左右子树都为空
            x.left = x.right = null;
            if (r == null) {
            // 表名此次循环是首次,操作的就是根节点
                // 根节点没有父节点
                x.parent = null;
                // 红黑树的根节点必须是黑色
                x.red = false;
                // x来指向这个根节点
                r = x;
            }
            else {
            // 此时插入红黑树的子节点
            
                K k = x.key;
                int h = x.hash;
                Class<?> kc = null;
                for (TreeNode<K,V> p = r;;) {
                    int dir, ph;
                    K pk = p.key;
                    if ((ph = p.hash) > h)
                    // 如果上一次遍历的节点的hash值ph大于当前节点的hash值h
                        dir = -1;
                    else if (ph < h)
                    // 如果上一次遍历的节点的hash值ph小于当前节点的hash值h
                        dir = 1;
                    else if ((kc == null &&
                              (kc = comparableClassFor(k)) == null) ||
                             (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);
                        
                    // xp指向上一次遍历的节点
                    TreeNode<K,V> xp = p;
                    if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    // dir<=0就将p指向左孩子,否则指向右孩子
                        // 当前节点parent指针指向父节点
                        x.parent = xp;
                        if (dir <= 0)
                            // 父节点的left指针指向当前节点
                            xp.left = x;
                        else
                            xp.right = x;
                        r = balanceInsertion(r, x);
                        break;
                    }
                }
            }
        }
        // root指针指向根TreeNode
        this.root = r;
        assert checkInvariants(root);
    }

    /**
     * Acquires write lock for tree restructuring.
     */
    private final void lockRoot() {
        if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
            contendedLock(); // offload to separate method
    }

    /**
     * Releases write lock for tree restructuring.
     */
    private final void unlockRoot() {
        lockState = 0;
    }

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

    /**
     * Returns matching node or null if none. Tries to search
     * using tree comparisons from root, but continues linear
     * search when lock not available.
     */
    final Node<K,V> find(int h, Object k) {
        if (k != null) {
            for (Node<K,V> e = first; e != null; ) {
                int s; K ek;
                if (((s = lockState) & (WAITER|WRITER)) != 0) {
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    e = e.next;
                }
                else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                                             s + READER)) {
                    TreeNode<K,V> r, p;
                    try {
                        p = ((r = root) == null ? null :
                             r.findTreeNode(h, k, null));
                    } finally {
                        Thread w;
                        if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                            (READER|WAITER) && (w = waiter) != null)
                            LockSupport.unpark(w);
                    }
                    return p;
                }
            }
        }
        return null;
    }

    /**
     * Finds or adds a node.
     * @return null if added
     */
    final TreeNode<K,V> putTreeVal(int h, K k, V v) {
        Class<?> kc = null;
        boolean searched = false;
        for (TreeNode<K,V> p = root;;) {
            int dir, ph; K pk;
            if (p == null) {
                first = root = new TreeNode<K,V>(h, k, v, null, null);
                break;
            }
            else if ((ph = p.hash) > h)
                dir = -1;
            else if (ph < h)
                dir = 1;
            else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                return p;
            else if ((kc == null &&
                      (kc = comparableClassFor(k)) == null) ||
                     (dir = compareComparables(kc, k, pk)) == 0) {
                if (!searched) {
                    TreeNode<K,V> q, ch;
                    searched = true;
                    if (((ch = p.left) != null &&
                         (q = ch.findTreeNode(h, k, kc)) != null) ||
                        ((ch = p.right) != null &&
                         (q = ch.findTreeNode(h, k, kc)) != null))
                        return q;
                }
                dir = tieBreakOrder(k, pk);
            }

            TreeNode<K,V> xp = p;
            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                TreeNode<K,V> x, f = first;
                first = x = new TreeNode<K,V>(h, k, v, f, xp);
                if (f != null)
                    f.prev = x;
                if (dir <= 0)
                    xp.left = x;
                else
                    xp.right = x;
                if (!xp.red)
                    x.red = true;
                else {
                    lockRoot();
                    try {
                        root = balanceInsertion(root, x);
                    } finally {
                        unlockRoot();
                    }
                }
                break;
            }
        }
        assert checkInvariants(root);
        return null;
    }

    /**
     * Removes the given node, that must be present before this
     * call.  This is messier than typical red-black deletion code
     * because we cannot swap the contents of an interior node
     * with a leaf successor that is pinned by "next" pointers
     * that are accessible independently of lock. So instead we
     * swap the tree linkages.
     *
     * @return true if now too small, so should be untreeified
     */
    final boolean removeTreeNode(TreeNode<K,V> p) {
        TreeNode<K,V> next = (TreeNode<K,V>)p.next;
        TreeNode<K,V> pred = p.prev;  // unlink traversal pointers
        TreeNode<K,V> r, rl;
        if (pred == null)
            first = next;
        else
            pred.next = next;
        if (next != null)
            next.prev = pred;
        if (first == null) {
            root = null;
            return true;
        }
        if ((r = root) == null || r.right == null || // too small
            (rl = r.left) == null || rl.left == null)
            return true;
        lockRoot();
        try {
            TreeNode<K,V> replacement;
            TreeNode<K,V> pl = p.left;
            TreeNode<K,V> pr = p.right;
            if (pl != null && pr != null) {
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // find successor
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { // p was s's direct parent
                    p.parent = s;
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    r = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            if (replacement != p) {
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)
                    r = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }

            root = (p.red) ? r : balanceDeletion(r, replacement);

            if (p == replacement) {  // detach pointers
                TreeNode<K,V> pp;
                if ((pp = p.parent) != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                    p.parent = null;
                }
            }
        } finally {
            unlockRoot();
        }
        assert checkInvariants(root);
        return false;
    }

    /* ------------------------------------------------------------ */
    // Red-black tree methods, all adapted from CLR

    static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                          TreeNode<K,V> p) {
        TreeNode<K,V> r, pp, rl;
        if (p != null && (r = p.right) != null) {
            if ((rl = p.right = r.left) != null)
                rl.parent = p;
            if ((pp = r.parent = p.parent) == null)
                (root = r).red = false;
            else if (pp.left == p)
                pp.left = r;
            else
                pp.right = r;
            r.left = p;
            p.parent = r;
        }
        return root;
    }

    static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                           TreeNode<K,V> p) {
        TreeNode<K,V> l, pp, lr;
        if (p != null && (l = p.left) != null) {
            if ((lr = p.left = l.right) != null)
                lr.parent = p;
            if ((pp = l.parent = p.parent) == null)
                (root = l).red = false;
            else if (pp.right == p)
                pp.right = l;
            else
                pp.left = l;
            l.right = p;
            p.parent = l;
        }
        return root;
    }

    // 该方法用来调整树,以符合红黑树规则
    // 最终返回符合红黑树规则的数据结构的根节点
    static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                TreeNode<K,V> x) {
        x.red = true;
        for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            if (xp == (xppl = xpp.left)) {
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                else {
                    if (x == xp.right) {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            }
            else {
                if (xppl != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                else {
                    if (x == xp.left) {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                               TreeNode<K,V> x) {
        for (TreeNode<K,V> xp, xpl, xpr;;)  {
            if (x == null || x == root)
                return root;
            else if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            else if (x.red) {
                x.red = false;
                return root;
            }
            else if ((xpl = xp.left) == x) {
                if ((xpr = xp.right) != null && xpr.red) {
                    xpr.red = false;
                    xp.red = true;
                    root = rotateLeft(root, xp);
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null)
                    x = xp;
                else {
                    TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                    if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                        xpr.red = true;
                        x = xp;
                    }
                    else {
                        if (sr == null || !sr.red) {
                            if (sl != null)
                                sl.red = false;
                            xpr.red = true;
                            root = rotateRight(root, xpr);
                            xpr = (xp = x.parent) == null ?
                                null : xp.right;
                        }
                        if (xpr != null) {
                            xpr.red = (xp == null) ? false : xp.red;
                            if ((sr = xpr.right) != null)
                                sr.red = false;
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateLeft(root, xp);
                        }
                        x = root;
                    }
                }
            }
            else { // symmetric
                if (xpl != null && xpl.red) {
                    xpl.red = false;
                    xp.red = true;
                    root = rotateRight(root, xp);
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                if (xpl == null)
                    x = xp;
                else {
                    TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                    if ((sl == null || !sl.red) &&
                        (sr == null || !sr.red)) {
                        xpl.red = true;
                        x = xp;
                    }
                    else {
                        if (sl == null || !sl.red) {
                            if (sr != null)
                                sr.red = false;
                            xpl.red = true;
                            root = rotateLeft(root, xpl);
                            xpl = (xp = x.parent) == null ?
                                null : xp.left;
                        }
                        if (xpl != null) {
                            xpl.red = (xp == null) ? false : xp.red;
                            if ((sl = xpl.left) != null)
                                sl.red = false;
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateRight(root, xp);
                        }
                        x = root;
                    }
                }
            }
        }
    }

    /**
     * Recursive invariant check
     */
    static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
        TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
            tb = t.prev, tn = (TreeNode<K,V>)t.next;
        if (tb != null && tb.next != t)
            return false;
        if (tn != null && tn.prev != t)
            return false;
        if (tp != null && t != tp.left && t != tp.right)
            return false;
        if (tl != null && (tl.parent != t || tl.hash > t.hash))
            return false;
        if (tr != null && (tr.parent != t || tr.hash < t.hash))
            return false;
        if (t.red && tl != null && tl.red && tr != null && tr.red)
            return false;
        if (tl != null && !checkInvariants(tl))
            return false;
        if (tr != null && !checkInvariants(tr))
            return false;
        return true;
    }

    private static final sun.misc.Unsafe U;
    private static final long LOCKSTATE;
    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = TreeBin.class;
            LOCKSTATE = U.objectFieldOffset
                (k.getDeclaredField("lockState"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

3.4 构造方法

// 创建一个新的空map,拥有默认的初始大小DEFAULT_CAPACITY = 16
public ConcurrentHashMap() {
	// 什么也不做
}

/**
 * 创建一个新的空map,拥有指定的初始大小,设置的好就能避免动态扩容带来性能消耗
 *
 * @param initialCapacity The implementation performs internal
 * sizing to accommodate this many elements.
 * @throws IllegalArgumentException initialCapacity为负数就抛出
 */
public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    // 如果initialCapacity大于等于2的29次幂那cap=2的30次幂
    // 否则cap 等于最接近且大于initialCapacity的一个2的N次幂
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    // cap赋值给sizeCtl         
    this.sizeCtl = cap;
}

注意上述方法求sizeCtl最终的结果如下

initialCapacitysizeCtl
>= MAXIMUM_CAPACITYMAXIMUM_CAPACITY
>=0sizeCtl>=2*initialCapacity,且是最接近2*initialCapacity的一个2的N次幂

比如
c=0, result=1(2的0次幂)
c=1, result=2
c=2, result=4
c=7, result=16

下面继续看其他参数不同的构造函数。

/**
 * 通过给定的map来创建ConcurrentHashMap
 * @param m the map
 */
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    // 注意这里sizeCtl = 16
    this.sizeCtl = DEFAULT_CAPACITY;
    putAll(m);
}

/**
 * 创建一个新的空map,拥有指定的初始大小和负载因子
 *
 * @param initialCapacity the initial capacity. The implementation
 * performs internal sizing to accommodate this many elements,
 * given the specified load factor.
 * @param loadFactor 负载因子,控制数组元素密度
 * @throws IllegalArgumentException if the initial capacity of
 * elements is negative or the load factor is nonpositive
 *
 * @since 1.6
 */
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}

/**
 * 创建一个新的空map,拥有指定的初始大小和负载因子还有个并发更新线程数
 *
 * @param initialCapacity 期望的初始容量大小,但会求一个最接近初始容量的2的N次幂
 * @param loadFactor 负载因子,控制数组元素密度
 * @param concurrencyLevel 估计的更新线程并发数. 可能用该值来作为大小的参考值
 * @throws IllegalArgumentException if the initial capacity is
 * negative or the load factor or concurrencyLevel are
 * nonpositive
 */
public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {                
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    // 尽可能使用更多bin,提交并发 
    if (initialCapacity < concurrencyLevel)   
        initialCapacity = concurrencyLevel;   // as estimated threads
    // 以下两步为找一个最接近初始容量的2的N次幂
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    // 复制给sizeCtl让他来控场
    this.sizeCtl = cap;
}

3.5 重要方法

3.5.1 put-放入

ConcurrentHashMap

/**
 * 映射指定的key到指定的value
 * 注意,key和value都必须非null
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 * @return key对应的旧value或当没有旧值时返回null
 * @throws NullPointerException if the specified key or value is null
 */
public V put(K key, V value) {
    // false代表upsert
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 空值验证
    if (key == null || value == null) throw new NullPointerException();
    // 得到扰动后的hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;

        if (tab == null || (n = tab.length) == 0)
        // 为空就初始化Entry数组
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        // 此时经过hash规则后找到的Node节点为空
            //创建一个next为空的链表头节点Node
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                // 成功创建就退出循环,否则重新执行循环过程     
                break;// no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
        // 此时为i下标处的Node,且他的hash值等于-1,说明该数组位置的链表需要扩容
            tab = helpTransfer(tab, f);
        else {
        // 此时数组不为空也没有匹配为空的头结点,也没有在扩容
            V oldVal = null;
            // 此时f是数组下标位置的链表的头结点
            // 注意这个地方synchronized,也就是说并发控制是基于Entry数组
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                // 还是从i能匹配到的下标位置是f节点
                    if (fh >= 0) {
                    // fh非负数说明不是特殊节点
                        // binCount用来统计该链表的节点数量
                        binCount = 1;
                        // 从头结点开始查找是否有相同的key存在
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                // 找到相同的key的节点,直接按需覆盖旧val
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                // 此时就退出循环了    
                                break;
                            }
                            // 此时没有找到对应的key
                            
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                            // 已经查找到尾节点,就直接新建Node,插到链表尾部
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                    // f是树根节点
                    
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            // 插入结果不为空代表插入失败,p为查找到的节点
                                                       
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                            // 更新旧值就行了
                                p.val = value;
                        }
                    }
                }
            }
            // binCount!=0意味着add了元素(不是新建数组或构建链表头)
            if (binCount != 0) {

                if (binCount >= TREEIFY_THRESHOLD)
                // 说明是插入了某个链表且容量达到变为红黑树的阈值
                    // 此时进行链表到红黑树改造
                    treeifyBin(tab, i);
                if (oldVal != null)
                // oldVal不为空说明有找到key对应Node,oldVal为旧值,直接返回
                    return oldVal;
                // 该节点是被新增的,就跳出循环    
                break;
            }
        }
        // 继续循环只会是两种情况:
        // 1. 新建了Entry数组
        // 2. 正在扩容,协助扩容
    }
    // 增加元素个数统计,并按需对数组扩容
    addCount(1L, binCount);
    // 返回null说明只是把新的值放到适当位置了,没有覆盖旧值
    return null;
}

可以看到,ConcurrentHashMap采用的是尾插法。
相比较,WeakHashMap使用的头插法,而ThreadLocalMap直接没有用链表而是nextIndex顺着往下找别的数组位置。

3.5.2 get-取出
public V get(Object key) {
   Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 得到扰动后的hash值
    int h = spread(key.hashCode());
    // 注意下,这里tabAt寻找目标节点的方法是CAS的
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        //找到了数组中对应的Node头结点且不为空,继续比较hash值和key
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
            // hash和key都能对应上,那就返回该Node的val
                return e.val;
        }
        else if (eh < 0)
        // 特殊结点
        	  // 利用每个特殊Node自己的find方法找到hash和key对应的值
            return (p = e.find(h, key)) != null ? p.val : null;
        // 不是特殊节点,就直接从链表第二个结点顺序查找
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    // 没找到就返回空
    return null;
}
3.5.3 size
public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}
3.5.4 mappingCount
/**
 * Returns the number of mappings. This method should be used
 * instead of {@link #size} because a ConcurrentHashMap may
 * contain more mappings than can be represented as an int. The
 * value returned is an estimate; the actual count may differ if
 * there are concurrent insertions or removals.
 * 返回键值对的数量,注意这是个long型
 * 因为ConcurrentHashMap的键值对数量可能超过int最大值
 * 
 * 同时需要注意的是,这是一个估计值,因为在统计数量同时可能在进行插入和删除
 *
 * @return the number of mappings
 * @since 1.8
 */
public long mappingCount() {
    long n = sumCount();
    return (n < 0L) ? 0L : n; // ignore transient negative values
}
3.5.5 扩容相关

扩容一览图

3.5.5.1 tryPresize
/**
 * 尝试预扩容以容纳给定数量的元素
 * @param size 给定元素的数量 (不需要完全精确)
 */
private final void tryPresize(int size) {
    // 求得扩容后的大小,如果大于最大容量一半就直接扩容到最大大小,否则用tableSizeFor计算
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
        tableSizeFor(size + (size >>> 1) + 1);
    int sc;
    while ((sc = sizeCtl) >= 0) {
    // 还没建表或已经初始化完成
        Node<K,V>[] tab = table; int n;
        if (tab == null || (n = tab.length) == 0) {
        // 注意此时table为空
        
            // n取max(sc,c)
            n = (sc > c) ? sc : c;
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            // 表示本线程独占初始化数组的权利,开始初始化
            
                try {
                    if (table == tab) {
                    // 表示没有被其他线程扩容指向新数组对象
                    
                        @SuppressWarnings("unchecked")
                        // 创建一个新大小的数组
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        // 全局table指针指向这个新数组
                        table = nt;
                        // sc = 0.75n,作为扩容阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // 扩容完成,更新sizeCtl
                    sizeCtl = sc;
                }
            }
        }
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
        // 目标扩容大小小于原来的扩容阈值或者超出或达到容量上线,直接返回
            break;
        else if (tab == table) {
        // table不为空,且没有被其他线程创建新数组,就准备扩容
        
            int rs = resizeStamp(n);
            if (sc < 0) {
            
                Node<K,V>[] nt;
                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);
        }
    }
}
3.5.5.2 helpTransfer-协助扩容

helpTransfer方法用途是协助扩容。

/**
 * 如果扩容正在进行就协助转移数据
 */
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
    Node<K,V>[] nextTab; int sc;
    // 判断数组不为空,且f节点为ForwardingNode,且nextTable不为空
    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;
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                // 调用transfer方法进行复制
                transfer(tab, nextTab);
                break;
            }
        }
        return nextTab;
    }
    return table;
}
3.5.5.3 resizeStamp
/**
 * Returns the stamp bits for resizing a table of size n.
 * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
 * 返回用于调整大小为n的表的大小的标记位
 * 
 */
static final int resizeStamp(int n) {
    // Integer.numberOfLeadingZeros方法的作用是返回无符号整型i的最高非零位前面的0的个数,包括符号位在内;
    // 如果i为负数,这个numberOfLeadingZeros方法将会返回0,因为符号位为1.
    
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
3.5.5.4 transfer-复制数组

ConcurrentHashMap中的transfer方法是十分复杂的,有如下特点:

  • 支持并发即多线程协助扩容
  • 无锁

扩容主要分为两步:

  1. 由一个线程创建一个指定大小为之前2倍的nextTable
  2. 然后将此前table指针指向的数组所有成员复制到nextTable。此阶段允许多线程辅助扩容。

假设扩容之前节点的分布如下,这里区分蓝色节点和红色节点,是为了后续更好的分析:
transfer方法
在上图中,第14号bin位置插入新节点之后,链表元素个数已经达到了8,且数组长度为16,优先通过扩容来缓解链表过长的问题.

注意到此transfer方法是没有加锁的,因为在调用此方法前都有CAS代码,类似如下:

if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
/**
 * 移动拷贝每个bin到新的数组中
 * 注意到此transfer方法是没有加锁的,因为在调用此方法前都有CAS代码
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    int n = tab.length, stride;
    // 根据NCPU来求步长
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        // subdivide range, 16
        stride = MIN_TRANSFER_STRIDE; 
    if (nextTab == null) {
    // initiating
        try {
            @SuppressWarnings("unchecked")
            // 创建一个容量扩大一倍的Node数组
            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;
        // transferIndex等于扩容前数组容量
        transferIndex = n;
    }
    // nextn为扩容后大小,是旧大小的2倍
    int nextn = nextTab.length;
    // 注意这里创建了一个ForwardingNode,其中保存了新数组nextTable的引用,
    // 在处理完每个槽位的节点之后被当做占位节点,表示该bin已经处理过了;
    // 主要目的是告知其他辅助扩容的线程不用处理该位置的bin
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    // 默认advace为真
    boolean advance = true;
    boolean finishing = false; // to ensure sweep before committing nextTab
    
    // 通过for循环处理每个bin中的链表
    // i指当前处理的bin序号,bound指需要处理的bin边界
    // 下面的分析以transferIndex=16,stride=16为例子分析
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        // 这个while循环主要目的是
        while (advance) {
            int nextIndex, nextBound;
            // 第1次for循环开始的时候,i=0,bound=0,transferIndex=16
            // 第2次for循环开始的时候,i=15,bound=0,transferIndex=0
            // 第17次for循环开始的时候,i=0,bound=0,transferIndex=0,--i=-1<bound
            if (--i >= bound || finishing)
            // 第1次for循环 --i=-1 < bound,不会进入
            // 第2次for循环 --i=14>bound
            // 直到16次循环 --i = 0,最后一次进入该分支
                advance = false;
            else if ((nextIndex = transferIndex) <= 0) {
            // 第1次for循环 transferIndex=16>0,不会进入
            // 第17次for循环,nextIndex=transferIndex=0,进入该分支
            // i=-1,代表开始结束流程
                i = -1;
                advance = false;
            }
            else if (U.compareAndSwapInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
                /** 
                 * 第一次进入while时会走这个分支
                 * CAS的把transferIndex设值为0, nextBound= 0
                 * bound = 0
                 * i = nextIndex-1=16-1=15
                 * 
                 * 后续不会再进入这个分支
                 */
                bound = nextBound;
                i = nextIndex - 1;
                // 结束while循环
                advance = false;
            }
        }
        // 注意,在我们例子中首次循环后i = 15,n=16,nextn=32
        // 第17次for循环i=-1
        if (i < 0 || i >= n || i + n >= nextn) {
        // 进入这个if才有可能退出循环
            int sc;
            if (finishing) {
            // 迁移完毕,nextTable重置为null(因为规定nextTable只有扩容时才不为null)
                nextTable = null;
                // table指向新的数组
                table = nextTab;
                // sizeCtl约为新容量的3/4
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
            // 每个线程完成复制后需要cas方式对sizeCtl减1,
            
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                // 回忆一下,初次开启扩容的线程会讲sizeCtl设为(resizeStamp(tab.length) << RESIZE_STAMP_SHIFT) + 2
                // 这个等式不成立代表当前线程并不是最后一个完成复制数组动作的线程
                    // 那么复制完毕后直接返回就行了
                    return;
                // 到这里,说明是最后一个完成transfer方法的线程
                // finishing为true代表扩容完毕
                // advance=true代表还需要循环一次
                finishing = advance = true;
                i = n; // recheck before commit
            }
        }
        else if ((f = tabAt(tab, i)) == null)
        // 在我们的这个示例中,15号bin是null,所以会走到这个循环里
            // 将此节点cas方式设为我们前面创建的ForwardingNode,告知不用处理该null节点
            // advance记录cas结果
            // 随后,继续for循环 
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
        // 注意,其他线程第一次进入循环的时候也会处理i=15,
        // 此时会发现这个位置Node的hash值为MOVED(-1),也就是说该Node是ForwardingNode
        // 也就是说,该节点已经被处理过了,此时就重置advance为true,继续处理更低位置的bin
            advance = true; // already processed
        else {
        // 一个线程来处理14号bin,我们的例子中该处是个链表
            // 注意这里加了同步锁,也就说只允许一个线程处理该位置的bin,其他线程等待
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                // f没被其他释放了同步锁的线程更改
                
                    Node<K,V> ln, hn;
                    if (fh >= 0) {
                     // fh非负数说明不是特殊节点
                        // 我们例子中这里n是16,所以这个运算结果要么为10000,要么为00000
                        // 也就是说通过这个运算就可以把链表上的元素分为两类
                        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) {
                        // 这代表最后一个与前一个节点分类不同的节点hash&n==0
                            // ln指向这个lastRun节点
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                        // 这代表最后一个与前一个节点分类不同的节点hash&n==1
                            // hn指向这个lastRun节点
                            hn = lastRun;
                            ln = null;
                        }
                        // 注意,下面的for循环直到lastRun就结束了
                        // 因为lastRun后的节点和lastRun节点必定是一个hash分类
                        // 他们自然组成一个链表,扩容后放在一个bin
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            // 将节点按hash分类重新连接为Node链表,注意这里顺序会变
                            if ((ph & n) == 0)
                                // ln最终指向该(hash&n=0)的Node组成的新链表头结点
                                // 这里用的是头插法
                                ln = new Node<K,V>(ph, pk, pv, ln);
                            else
                                // hn最终指向该(hash&n=1)的Node组成的新链表头结点
                                hn = new Node<K,V>(ph, pk, pv, hn);
                        }
                        // 将ln节点放到新数组i位置
                        setTabAt(nextTab, i, ln);
                        // 将hn节点放到新数组i+n位置
                        setTabAt(nextTab, i + n, hn);
                        // 将老数组的i位置设为ForwardingNode
                        // 表明该节点已经处理完毕可以跳过
                        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;
                        // lc hg分别统计新的树的节点个数
                        int lc = 0, hc = 0;
                        // 以链表的形式遍历红黑树
                        for (Node<K,V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            // 注意这里新建了TreeNode,只保留了hash、key、val
                            TreeNode<K,V> p = new TreeNode<K,V>
                                (h, e.key, e.val, null, null);
                            // 这里也是跟普通节点一样按hash&n的结果按是否为0进行分类    
                            // 这里的操作方式就是同一类的节点顺序构建双向链表
                            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.如果低于阈值(6),就将新的红黑树降级为链表;
                        // 2.否则判断另一hash类型节点个数是否大于0 
                        // 2.1如另一hash类总数>0,就创建新的TreeBin存放该类型红黑树
                        // 2.2否则就直接把旧的TreeBin作为新的当前类型的TreeBin
                        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;
                        
                        // 将ln TreeBin节点放到新数组i位置
                        setTabAt(nextTab, i, ln);
                        // 将hn TreeBin节点放到新数组i+n位置
                        setTabAt(nextTab, i + n, hn);
                        // 将老数组的i位置设为ForwardingNode,表明该节点已经处理完毕可以跳过  
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                }
            }
        }
    }
}

3.6 辅助方法

3.6.1 spread
// 最高位为0,31个1
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

static final int spread(int h) {
		// h 异或 (h 无符号右移 16位) ,然后 逻辑与 0x7fffffff
        return (h ^ (h >>> 16)) & HASH_BITS;
}

方法里的参数hhashCode

  • 原始h
    0011101101100011 1111111100101110
  • h >>> 16
    0000000000000000 1011101101100011
  • h ^ (h >>> 16)
    0011101101100011 0100010001001101
  • h ^ (h >>> 16) & HASH_BITS;
    0011101101100011 0100010001001101

需要注意的是,这里HASH_BITS = 0x7fffffff,也就是
011111111111111 11111111111111111

注意到首位为0,作用就是将前面计算的结果最高位(符号位)强制设为0,避免出现负数。因为hash值为负数时,在ConcurrentHashMap中有特殊设计:

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

现在看来,spread这么做的目的无非就是将高16位和低16位融合增加随机性,同时又保留了原有高位,避免低位变化少造成的hash不变最终导致元素分布不均的情况。这样可以最大限度的避免碰撞。而且这一切都是位运算,效率极高。

jdk说明
散列(XORs) higher bits of hash to lower and also forces top bit to 0.
Spreads (XORs)将hash值的高16位右移到低16位,然后将最高位(符号位)置为0.

Because the table uses power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide.(Among known examples are sets of Float keys holding consecutive whole numbers in small tables.)
因为此表用二次掩码,所以仅在当前掩码之上的位上变化的散列集将总是发生冲突(其中已知的例子是Float键集在小表格中保持连续的整数)

So we apply a transform that spreads the impact of higher bits downward.
所以我们采用一个变换,它可以向下扩展高位的作用力

There is a tradeoff between speed, utility, and quality of bit-spreading.
我们需要在速度,可用性和位散列的质量之间来权衡

Because many common sets of hashes are already reasonably distributed (so don’t benefit from spreading), and because we use trees to handle large sets of collisions in bins,
由于许多常见的数据集合的hash值已经合理分布(所以不会从散列中获益),还因为我们用树来处理bin中大量的集合冲突,

we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.
所以我们仅需要对一些移位进行异或运算,这是尽可能的低开销方式来减少系统性能损失, 以及合并由于表边界而不可用于索引计算的最高位的影响。

3.6.2 tableSizeFor
/**
 * 通过给d定的期望容量,返回某个合适的2的次幂结果
 * 具体来说,他这个算法就是把从第一个非0高位开始往后都设为1,最后结果再加1
 * 最后得到大于输入参数且比较接近的2的次幂
 * 比如
 * c=1,result=1(2的0次幂)
 * c=2,result=4
 * 
 */
private static final int tableSizeFor(int c) {
    // 假设c = 139 = 10001011
    int n = c - 1; // n = 138 = 10001010
    n |= n >>> 1;  // n = 11001111
    n |= n >>> 2;  // n = 11111111
    n |= n >>> 4;  // n = 11111111
    n |= n >>> 8;  // n = 11111111
    n |= n >>> 16; // n = 11111111 = 255
    //返回的值为256 = 2的8次幂
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 
}
3.6.3 initTable

前面说过,Entry数组是懒初始化的,会在第一次插入值(putVal)的时候才被调用。
initTable()

/**
 * 使用sizeCtl中记录的大小来初始化Entry数组
 */
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            // 说明别的线程在初始化数组或扩容,放弃CPU时间片
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            // CAS前sizeCtl不为负数,然后成功以CAS方式将sizeCtl设为了-1,表示初始化开始
            try {
                // 再判断以下以防是别的线程将数组初始化了但重置了sizeCtl
                if ((tab = table) == null || tab.length == 0) {
                    // 进入这里说明是本线程首次初始化
                    // sc如果为0 就设为默认容量16
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    // 创建一个大小为n的Entry数组
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    // table指向这个新数组
                    table = tab = nt;
                    // 约等于容量n的3/4
                    sc = n - (n >>> 2);
                }
            } finally {
                // 赋值给控场的sizeCtl
                sizeCtl = sc;
            }
            break;
        }
    }
    // tab指针返回
    return tab;
}

值得注意的是,initTable方法是对SIZECTL进行compareAndSwapIntCAS的操作的方式来避免线程冲突。

3.6.3 tabAt-找到i在数组中位置
// 通过移位的方法,找到i在tab中的位置的头结点Node
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
3.6.4 casTabAt
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
    // CAS的方式把Node v放到数组的下标i位置,根据成功与否返回结果                                    
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
3.6.5 putTreeVal
/**
 * Finds or adds a node.
 * @return 如果是增加就返回null,否则返回查找到的节点
 */
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk;
        if (p == null) {
            first = root = new TreeNode<K,V>(h, k, v, null, null);
            break;
        }
        else if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
        // 找到了就返回该节点
            return p;
        else if ((kc == null &&
                  (kc = comparableClassFor(k)) == null) ||
                 (dir = compareComparables(kc, k, pk)) == 0) {
            if (!searched) {
                TreeNode<K,V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&
                     (q = ch.findTreeNode(h, k, kc)) != null) ||
                    ((ch = p.right) != null &&
                     (q = ch.findTreeNode(h, k, kc)) != null))
                    return q;
            }
            dir = tieBreakOrder(k, pk);
        }

        TreeNode<K,V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            TreeNode<K,V> x, f = first;
            first = x = new TreeNode<K,V>(h, k, v, f, xp);
            if (f != null)
                f.prev = x;
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            if (!xp.red)
                x.red = true;
            else {
                lockRoot();
                try {
                    root = balanceInsertion(root, x);
                } finally {
                    unlockRoot();
                }
            }
            break;
        }
    }
    assert checkInvariants(root);
    return null;
}
3.6.6 treeifyBin

这个方法用于将过长的链表转换为TreeBin对象。但是他并不是直接转换,而是进行一次容量判断:

  • 如果当前容量没有达到转换的最小值(64个),直接进行数组扩容操作;
  • 如果满足条件才将链表的结构抓换为TreeBin 。这与HashMap不同的是,它并没有把TreeNode直接放入红黑树,而是利用了TreeBin这个小容器来封装所有的TreeNode.
/**
 * 在指定的index处替换该位置的链表上的所有节点,
 * 除非表太小,这种情况需要调整大小即扩容
 * 这里的index就是Node数组中的索引
 */
private final void treeifyBin(Node<K,V>[] tab, int index) {
    Node<K,V> b; int n, sc;
    if (tab != null) {
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
        // 小于MIN_TREEIFY_CAPACITY,预扩容Node数组
            tryPresize(n << 1);
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
        // 否则将链表转为红黑树
            // 锁定Node数组该位置的链表的头结点
            synchronized (b) {
                // 以下这个判断的作用是,防止别的线程刚刚释放了synchronized (b),将该位置已经指向了其他Node
                if (tabAt(tab, index) == b) {
                    // hd暂存首个节点即根节点,tl暂存上一个节点即当前节点的父节点
                    TreeNode<K,V> hd = null, tl = null;
                    // 下面这个循环的作用是用旧的Node链表构建TreeNode双向链表
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        TreeNode<K,V> p =
                            new TreeNode<K,V>(e.hash, e.key, e.val,
                                              null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    // 构建一个红黑树TreeBin,然后CAS放入Node数组的index位置
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}
3.6.7 addCount
/**
 * Adds to count, and if table is too small and not already
 * resizing, initiates transfer. If already resizing, helps
 * perform transfer if work is available.  Rechecks occupancy
 * after a transfer to see if another resize is already needed
 * because resizings are lagging additions.
 * 添加元素数量,
 * 而且需要判断数组是否过于拥挤,是否需要启动扩容
 * 如果已经开始扩容,就协助扩容
 * 
 * 在扩容移动元素后完成后重新检查占用情况,
 * 看是否已经需要再一次调整大小,因为addCount方法总是在增加元素之后调用
 *
 * @param x 要增加的元素数量
 * @param check if <0, don't check resize, if <= 1 only check if uncontended
 */
private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // 注意这个地方,首次进入时counterCells为null,所以会:s=b+x=x
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(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.compareAndSwapLong(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;
        // 下面这行代码如果是首次进入时,sc=sizeCtl是修正后的扩容参考值
        // 比如容量=16,sc=12
        // 而s=x=1
        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.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();
        }
    }
}
3.6.8 sumCount

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;
}
3.6.9 untreeify
/**
 * 将树降级为链表
 * 返回新链表的头结点
 */
static <K,V> Node<K,V> untreeify(Node<K,V> b) {
    // hd存新链表头结点,tl存新链表尾节点
    Node<K,V> hd = null, tl = null;
    // 从b开始以链表顺序遍历老的链表
    for (Node<K,V> q = b; q != null; q = q.next) {
        // 构建新的Node
        Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null);
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    // 最后返回头结点
    return hd;
}

3.7 源码总结

这个源码还是有一些地方没太看明白,比如各种移位运算,真的很难看明白作者要表达啥意思。
不过总的核心思想我们是弄明白了,希望各位能指出错误,谢谢。

0x04 其他思考

4.1 为什么一定要用红黑树?

为什么ConcurrentHashMap一定要用红黑树,而不是其他树比如AVL树?

  • 红黑树不是高度平衡树,而是规定每个节点必须是红黑色之一,用非严格的平衡来换取增删节点时候旋转次数的减少,任何不平衡都会在三次旋转之内解决(特别是删除时,红黑树3次旋转,AVL树logN 次)。具体来说,红黑树插入和删除操作改变树的平衡性的概率要远远小于AVL,因为他并非高度平衡树,这就意味着需要旋转调整平衡性的次数更少。红黑树只是会因为高度不平衡所以在最差情况查找效率低于AVL树。在处理平衡时,红黑树需要做变色操作,时间复杂度为O(logN)。但由于操作简单,所以在实践中这种变色非常快速。
  • 而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高。

4.2 为什么get方法不用加锁?

主要是因为Node的val用了volatile,会使得新的变动对所有线程可见(新变动立刻刷入主存,让所有线程关于val的工作缓存失效,强制去主存读最新值)

0xFF参考文档

HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!

ConcurrentHashMap源码分析_JDK1.8版本

ConcurrentHashMap总结

深入分析ConcurrentHashMap1.8的扩容实现

关于红黑树更多内容,可以参考占小狼的以下文章:
ConcurrentHashMap的红黑树实现分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值