ConcurrentHashMap 源码分析记录
ConcurrentHashMap继承了AbstractMap<K,V>,并实现了ConcurrentMap<K,V>, Serializable 接口
1、源码分析
A、成员变量
//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认初始容量
private static final int DEFAULT_CAPACITY = 16;
//最大数组容量,减8的原因是预留类加载内存空间
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//默认的并发级别
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//默认的加载因子
private static final float LOAD_FACTOR = 0.75f;
//树化的阈值
static final int TREEIFY_THRESHOLD = 8;
//链化的阈值
static final int UNTREEIFY_THRESHOLD = 6;
//树化的最小数组容量(树化的条件之一)
static final int UNTREEIFY_THRESHOLD = 6;
//扩容时调用多个线程帮助扩容,每个线程分到的最小任务量
private static final int MIN_TRANSFER_STRIDE = 16;
//该四个变量为Node对象的hash变量值
static final int MOVED = -1; // hash for forwarding nodes
static final int TREEBIN = -2; // hash for roots of trees
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
//当前机器的CPU数
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存储Node对象的数组
jdk8中ConcurrentHashMap共有5个构造方法,构造方法都没有对内部的数组做初始化,只是对一些变量的初始值做了处理,
jdk8的ConcurrentHashMap的数组初始化是在第一次添加元素时完成的
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
}
//没有维护任何变量的操作,如果调用该方法,则默认数组长度为16
public ConcurrentHashMap() {
}
//传递进来一个自定义的容量大小,ConcurrentHashMap会基于这个容量参数算出一个比该值大的最小2的幂次方作为数组的初始容量
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
//初始容量的计算:先判断传入的值是否大于最大容量的一半,大于则初始容量设为最大容量的一半
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
//否则传入参数:传入参数+传入参数的右移1位+1进行移位运算得到初始容量,如传入15,则进行移位运算的参数为15+7+1 = 23,那么移位运算得到的大于该参数的最小的2的幂次方,大于23的最小幂次方为32
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
//将初始容量赋值给sizeCtl
this.sizeCtl = cap;
}
注意:调用这个方法,即使传入的是2的幂次方数,得到的初始容量也比该数大,与jdk7不同。根据当前集合的预计长度(元素个数),建议给一个初始容量进行实例构造,以免后期频繁扩容,影响性能
//初始化一个传入Map集合的ConcurrentHashMap
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
//将默认容量赋值给sizeCtl
this.sizeCtl = DEFAULT_CAPACITY;
//将传入的集合所有元素放到hashmap中
putAll(m);
}
//传入自定义的容量和加载因子
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
//构造自定义的容量和加载因子及预估的并发线程数量的实例
//initialCapacity初始容量。
//loadFactor用于建立初始表大小的负载因子(表密度)
//concurrencyLevel并发更新线程的估计数量。
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;
}
//值为0:代表数组未初始化,且初始容量为16
//值为正数:1、若数组未初始化,则值为数组的初始容量;2、若数组已经初始化,则值为数组的扩容阈值
//值为-1:代表数组正在进行初始化
//值小于0且不为-1,表示数组正在扩容,-(1+n),表示此时有n个线程正在共同完成扩容操作
private transient volatile int sizeCtl;
//put方法
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
//实现元素添加的方法,不允许空值空键
final V putVal(K key, V value, boolean onlyIfAbsent) {
//空值空键判断
if (key == null || value == null) throw new NullPointerException();
//通过键的hashcode方法来计算扰动,减少hash冲突,得到一个hash值,该方法得到的hash值一定为正数
//该值一定为正数,方便后面添加节点时判断该节点的类型
int hash = spread(key.hashCode());
int binCount = 0;
//进入死循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//判断对象数组是否为空及数组的容量是否为0
if (tab == null || (n = tab.length) == 0)
//初始化对象数组
tab = initTable();
//不为空则通过hash值与上数组容量-1得到一个下标,如果该下标的元素为空,则新建一个Node对象放到 该下标位置
//tabAt方法是调用Unsafe的本地方法原子性的判断该索引位置是否为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//下标为空的Cas计算方法
//通过CAS计算并将新的Node对象放到数组对应的下标处
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) {
//这里的判断是防止当前位置的元素添加完成之后进行树化,则当前位置的头节点会改变
//如果元素还是一样,则说明该位置并没有进行树化或者数组没有进行扩容
if (tabAt(tab, i) == f) {
//如果该节点的hash值大于等于0,则说明该节点为链表
if (fh >= 0) {
//binCunt为记录链表的元素个数
binCount = 1;
//遍历链表,将新元素加到链表尾部
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果该索引位置的元素的hash值=传入的键的hash值,则说明键相同,替换 value值为新value值
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//存储原来的value值
oldVal = e.val;
if (!onlyIfAbsent)
//替换为新的value值
e.val = value;
break;
}
//将索引位置处的元素赋值给pred
Node<K,V> pred = e;
//如果该节点的后节点为空
if ((e = e.next) == null) {
//将后节点设为传入的键值对Node对象
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果该节点的hash值为负数,则为红黑树的节点
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//调用putTreeVal方法对传入的键值进行树节点的判断和添加
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
//添加或查找成功,则记录原value
oldVal = p.val;
if (!onlyIfAbsent)
//如果有相同的键,则替换value
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果binCount大于等于8,则进入树化方法
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
//如果有相同键,则返回被替换的value
if (oldVal != null)
return oldVal;
break;
}
}
}
//维护hashmap的元素个数
addCount(1L, binCount);
return null;
}
//hash值的扰动算法,HASH_BITS的值为0x7fffffff,二进制0111 1111 1111 1111 1111 1111 1111 1111,最高位为0,与运算任何数都为0,因此得到的数一定为正数
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
//初始化数组的方法
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
//数组为空且容量为0
//CAS+自旋,保证线程安全,对数组进行初始化操作
while ((tab = table) == null || tab.length == 0) {
//此时sizeCtl为初始化的0
if ((sc = sizeCtl) < 0)
//如果一个线程拿到了cpu执行权,则放弃,因为此时已有其他线程正在进行初始化
Thread.yield(); // lost initialization race; just spin
//通过CAS方法将sc设为-1,以便后面线程进来的时候进行放弃执行权的判断
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//再次进行判断,doublechecklock,双检机制
if ((tab = table) == null || tab.length == 0) {
//此时sc为-1,因此n为默认容量
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//创建一个Node数组对象,容量为n
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//将创建好的数组对象赋值给table
table = tab = nt;
//对n进行移位运算得到12
sc = n - (n >>> 2);
}
} finally {
//将sc的值赋给sizeCtl,因此此时sizeCtl的值为map的扩容阈值
//初始化完成后,sizeCtl为扩容阈值
sizeCtl = sc;
}
break;
}
}
return tab;
}
//维护集合长度的方法
private final void addCount(long x, int check) {
//x为1,
CounterCell[] as; long b, s;
//判断counterCells是否为空或者通过cas方法判断x能否加成功
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
//如果counterCells数组为空
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//进入方法进行数组的value值的计算
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
//获取集合元素个数
s = sumCount();
}
//添加时check一定大于等于0
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,说明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);
}
//此处将SIZECTL设置为-1,因为rs向左移17位后最高位变为了1,
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//同样会进入扩容方法
transfer(tab, null);
s = sumCount();
}
}
}
//集合元素的个数
final long sumCount() {
//对baseCount和counterCells数组中的值进行累加,得到集合中的元素个数
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种情况:添加第一个元素时,CounterCell[]数组为空,则会进入最下面的分支,对baseCount进行++操作,操作成功,则break;如果添加不成功,则会进入中间的分支,创建一个CounterCell[]数组,并初始化长度为2,如果原有数组不为空的话就扩容,扩容的最大值为cpu数,并进行元素copy;如果CounterCell[]数组不为空,则会维护CounterCell[]中CounterCell对象的value值,将添加的x值放到对象中
private final void fullAddCount(long x, boolean wasUncontended) {
//x是上面方法传入的1,wasUncontended为true
int h;
//计算数组的随机下标
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
//进入死循环
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
//数组有值,说明有多线程在操作
if ((as = counterCells) != null && (n = as.length) > 0) {
//数组的下标处值为空
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
//新建一个CounterCell对象
CounterCell r = new CounterCell(x); // Optimistic create
//将cellsBusy又设为1
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
//将新建的CounterCell对象放到数组中
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
//判断是否再进行扩容
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
else if (!collide)
collide = true;
//如果cellsBusy为0,则通过CAS操作将其设为1,保证线程安全
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
//新建一个CounterCell数组,容量为n值左移1位
CounterCell[] rs = new CounterCell[n << 1];
//遍历数组,并将counterCells数组元素赋给该数组
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
//重新将该值设为0
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//重新计算随机值
h = ThreadLocalRandom.advanceProbe(h);
}
//如果baseCount没有加成功,则创建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
}
}
维护数组长度时,map集合有一个baseCount变量,首先对该变量进行操作,每添加一个元素,该值就加1,如果因为多线程没有+1成功,则会设置一个新的数组CounterCel[] counterCels,里面的counterCel对象有一个属性value,数组初始容量为2,会计算一个数组下标,将传入的x值加到下标位置的对象value值上,如果线程核数大于数组初始值,会进行数组扩容,并把数组中的对象copy到新数组中,扩容的量最多为cpu核数,最后通过sumCount()方法得到集合中的元素个数
扩容方法
//扩容方法,其原理是先判断cpu的线程数,然后确保每个线程的最小任务量为16,再新建一个数组,容量为原数组的2倍;
//又新建一个ForwardingNode对象,作为扩容过程的标识对象,并把新建的数组传给该对象,ForwardingNode对象里的hash值为MOVED,当数据迁移时,每迁移一个元素,就会新建一个ForwardingNode对象放在原数组原位置,标识这是一个正在迁移的集合,因此在前面进行put时会判断索引位置的元素的hash值,如果hash值为MOVED,表示数组正在扩容,线程就会去协助扩容
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//计算每个线程协助迁移数据的任务量
//每个线程最小任务量为16
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
//新建一个数组,容量为原来的2倍
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
//新建一个fwd对象,传入新的数组
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
//详细分配线程的任务量
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//判断扩容是否结束
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
//如果当前位置为空,则直接塞一个fwd对象
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
//如果当前位置的元素hash值为MOVED,则表示迁移过了
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
//否则进入真正的迁移动作
else {
//对要迁移的位置加锁,对头节点加锁
synchronized (f) {
if (tabAt(tab, i) == f) {
//定义低位节点、高位节点
Node<K,V> ln, hn;
//如果元素hash值大于等于0,则为链表
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
//遍历链表,将与运算计算的值为0的设为低位链表,不为0的设为高位链表
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);
//迁移完,将该位置元素设为fwd对象
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;
}
}
//树的迁移与HashMap逻辑类似
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;
}
}
}
}
}
}
扩容的原理:多个线程操作时,会从数组的尾部给每个线程分配任务,最小任务量是16,每个线程做完自己的任务后还会循环领任务,如果任务都做完了,最后做完的线程会退出扩容操作。扩容时,会给当前遍历到的桶位加锁,避免其他线程对该桶位进行其他操作,将数据迁移完成后,会在该桶位放一个fwd对象,其hash值为MOVED,用以在put时判断当前桶位的状态。JDK1.8中ConcurrentHashMap的优化就在于取代了1.7中分段锁的机制,进一步将锁的粒度缩小到每个桶位,这样,其他桶位在进行操作时不会受到影响,效率大大提高。