JDK8中ConcurrentHashMap源码解析
在介绍ConcurrentHashMap源码之前,首先需要了解以下几个知识
1、JDK1.8中ConcurrentHashMap的基本结构
2、并发编程的三个概念:可见性,原子性,有序性
3、CAS(CompareAndSwap):比较和交换,是原子性操作,属于乐观锁的一种实现。
4、java中的锁机制
先简单看下ConcurrentHashMap类在jdk1.8中的设计,由数组+链表+红黑树构成,其基本结构如图所示:
/** * The array of bins. Lazily initialized upon first insertion. * Size is always a power of two. Accessed directly by iterators.
* 数组结构。采用第一次插入式才初始化的懒惰模式。容量总是2的幂次方。可通过迭代直接访问。 */ transient volatile Node<K,V>[] table; // volatile保证线程间可见
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; }
接下来看一下初一些重要的初始化变量
/** * 初始化默认容量 */ private static final int DEFAULT_CAPACITY = 16;
/**
* 最大容量 */ private static final int MAXIMUM_CAPACITY = 1 << 30;
/** * 当容量达到当前最大容量的75%时,进行扩容操作 */ private static final float LOAD_FACTOR = 0.75f;
/** * 当链表长度>=8,转换为红黑树 */ static final int TREEIFY_THRESHOLD = 8; /** * 当红黑树节点个数<=6,转换为链表 */ static final int UNTREEIFY_THRESHOLD = 6;
/** * 记录容器的容量大小,通过CAS更新 */ private transient volatile long baseCount; /** * 初始化和扩容控制参数。为负数时表示table正在被初始化或resize:-1(初始化),-(1+扩容线程数)
* sizeCtl默认值为0,大于0是扩容的阀值 */ private transient volatile int sizeCtl;
最后我们从put方法正式进入源码分析
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; // while(true)循环,不断的尝试,因为在table的初始化和casTabAt用到了compareAndSwapInt、compareAndSwapObject for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; // 如果数组(HashMap)还未初始化,进行初始化操作(CAS操作) if (tab == null || (n = tab.length) == 0) tab = initTable(); // 计算新插入数据在数组(HashMap)中的位置,如果该位置上的节点为null,直接放入数据(CAS操作) 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 } // 如果该节点的hash值为MOVED,说明正在进行扩容操作或者已经扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); // else { V oldVal = null; synchronized (f) { // 对特定数组节点tab[i]加锁 if (tabAt(tab, i) == f) { // 判断tab[i]是否有变化 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)))) { // 如果新插入值和tab[i]处的hash值和key值一样,进行替换 oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { // 如果此节点为尾部节点,把此节点的next引用指向新数据节点 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) { if (binCount >= TREEIFY_THRESHOLD) //如果数组节点的链表长度超过限定长度,转换为一颗红黑树 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }
上面代码已经关键部分已经做了注释,一些函数不再细讲。