【Java】ConcurrentHashMap源码解析

一直想结合源码看清ConcurrentHashMap的真面目,但是每次打开源码,看一段时间都会有看不下去的感觉,换一种方式,通过debug,一步步看清ConcurrentHashMap的实现原理。

真是换一个方式,换一种心情。

ConcurrentHashMap的总体介绍
1. 无参构造函数是空的,即没有初始化任何属性
2. key和value均不能为null
3. hash是key的hashCode进行spread之后的结果,即将hashCode的高16位和低16位异或并将第一位置为0,即保证是正数的结果
4. 下标定位通过[(n-1)&hash]的方式,该表达式得到的值会小于等于n-1,因为当hash的二进制数全为1时,表达式的值最大为n-1
5. 获取元素的方式是tabAt,通过Unsafe的getObjectVolatile的方式得到,即每次从主内存中读取数据
6. 设置值的方式是casTabAt,通过Unsafe的compareAndSwapObject方式设置值,由于设置值的方式是原子性的,所以无需加锁
7. Node节点和TreeBin节点的不同在于,Node节点有hash和key值,由于这两个属性被设置为final类型,因此不能被继承,所以在put值有冲突的时候,可以通过hash值是否大于0来判断是否是链表
8. 判断数组或者链表中是否存在某节点,判断条件为(1)hash相等 && (2)key是相同的对象[即内存地址一样],或者key的值相等[即equals方法判断相等]

属性说明
sizeCtl: 用于集合初始化和扩容,不同的值代表不同的含义,若为负,表示正在被初始化,或者正在被扩容,初始化时为-1,扩容时为-(1 + 活跃的扩容线程的数量)
如果集合为空,用于存储创建集合时的集合大小,默认是0,在集合初始化结束之后,用于存储下一次需要扩容时集合中元素个数,第一次是0.75*n。
DEFAULT_CAPACITY: 集合的默认初始化大小,默认是16
TREEIFY_THRESHOLD: 将用于解冲突的链表转化为红黑树的阀值,该值必须大于2,并且至少应该是8,否则无法对应将链表转为红黑树的消耗

Unsafe说明
compareAndSwapXXX(Object var1, long var2, int var3, int var4)
var1: 需要被修改属性的对象
var2: 需要被修改的属性在该对象中的偏移量,用该偏移量来找到属性,从而修改其值
var3: 期望值
var4: 新值,即修改之后的值

初始化过程
1. initTable时采用自旋+CAS的方式初始化集合
2. 初始化前通过sizeCtl属性进行前置判断,若为负,则已有线程在初始化,直接yield 

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    // 自旋
    while ((tab = table) == null || tab.length == 0) {
        // 已有线程在初始化
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        // CAS赋值,开始初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 二次判断
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
} 

计数过程
计数走addCount函数,通过CAS赋值,计数器每走一次都需要判断当前元素个数是否达到了sizeCtl,若是,则进行扩容操作 

扩容过程
1. 条件:当前计数大于等于sizeCtl && 数组不为空 && 数组的长度小于MAXIMUM_CAPACITY[1 << 30]
2. 通过resizeStamp函数获取扩容的stamp:Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1))
3. 若sizeCtl小于0,说明有其他线程在扩容
4. 若sizeCtl不小于0,说明当前线程是第一个线程进行扩容的,将sizeCtl原子化设为负值[不为-1时表示扩容],rs << RESIZE_STAMP_SHIFT) + 2
rs即为通过resizeStamp函数获取扩容的stamp
5. 通过transfer函数扩容拷贝
  默认扩容后的集合大小是原来集合大小的两倍

6. 扩容过程还在跟进中。。。

get过程分析
先通过hash得到所在位置上的元素
a. 元素值为null,直接返回null
b. 如果该元素和传入的key值的hash一致,则再通过地址或者equals方法判断是否是同一个key,
c. 如果hash值不一样,判断元素的hash值是否小于0,若是,则说明是通过红黑树解冲突,则通过key的hash值和key在红黑树中查询该key对应的value,若未找到,则返回null
d. 如果hash值不一致,也不是红黑树解冲突,则说明是链表结构,判断对应的key在链表中是否存在,若存在,则返回key对应的value,否则返回null

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());
    // 获取元素
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        // hash一致,则再通过地址或者equals方法判断是否是同一个key
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // hash值小于0,说明是通过红黑树解冲突,在红黑树中查询该key对应的value
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 链表结构,判断对应的key在链表中是否存在
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    // 通过hash获取的元素值为null,直接返回null
    return null;
} 

put过程分析
1. put == putVal(K, V, false) -> 如果key存在,新的value值回覆盖旧值,返回旧值
2. putIfAbsent == putVal(K, V, true) -> 如果key存在,不覆盖,直接返回集合中对应value值
3. put的循环过程
  a. 集合还未初始化,走初始化流程,即Entry数组初始化的时候是第一次put值的时候进行的
  b. 若通过hash定位后的数组对应位置上的值为空,则直接将值放到数组中的对应下标位置上[CAS获取和设值],break
  c. 若hash定位后对应位置上的元素的hash值为MOVED[-1],表示集合正在扩容,则走helpTransfer流程,帮助扩容
  d. 在hash定位后对应位置上的元素上通过synchronized加锁,即将锁加在桶上,然后进行元素的二次判断,防止多线程场景下的不一致
    (1) 通过传入key的hash定位后的数组中的元素的hash值是否大于0判断桶中的是否链表,若是链表[可能只有在数组中的一个元素],则遍历链表,判断是进行新值覆盖旧值[已存在该节点并且onlyIfAbsent==false]或者将新值添加到链表最后,并通过binCount局部变量获取解冲突之后的链表长度;
    (2) 若为红黑树[即通过hash定位的数组中的元素为红黑树的节点],通过putTreeVal方法判断树中是否已经存在值相同的节点,如果存在,则新值换旧值[已存在该节点并且onlyIfAbsent==false],否则,将节点添加到树中,并设置binCount为2;
    判断binCount是否大于等于TREEIFY_THRESHOLD,若是,则通过treeifyBin函数将链表转换为红黑树
    判断oldVal是否为空,若不为空,直接返回旧值,说明没有新增元素,不需要走addCount计数流程,否则break
4. binCount的作用
  用binCount局部变量表示解冲突之后的链表的长度,该局部变量是基于内存的,每次解冲突的时候,均需遍历链表,将新增元素加到链表的最后,或者覆盖原来的旧值[已存在该节点并且onlyIfAbsent==false],然后通过该变量获取接冲突之后链表的长度[若是新增元素,则binCount不包括数组中的首元素,否则包括],并通过该变量和TREEIFY_THRESHOLD相比较,判断是否要将链表转换成红黑树;
  若为红黑树,则binCount直接设置为2,因为转为红黑树的阀值TREEIFY_THRESHOLD必须大于2
5. 计数通过addCount函数加1

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // key和value均不能为null
    if (key == null || value == null) throw new NullPointerException();
    // 获取hash
    int hash = spread(key.hashCode());
    // 解冲突之后链表的长度,若为红黑树,即为2
    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();
        // 对应位置上的值为空,直接赋值
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        // 集合正在扩容,则走helpTransfer流程,帮助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 桶上加锁
            synchronized (f) {
                // 二次判断
                if (tabAt(tab, i) == f) {
                    // 链表结构
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // 红黑树结构
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        // 红黑树的binCount为2
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 判断是否要将链表转换成红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 计数
    addCount(1L, binCount);
    return null;
}

 

原文地址:https://www.cnblogs.com/sqdmydxf/p/7688276.html