ConcurrentHashMap在JDK1.7和 JDK1.8的变化
JDK1.7时,ConcurrentHashMap是一个Segment数组,对Segment数组中的元素进行加锁,数据结构如下
在JDK1.8中,取消了Segment,而直接使用Node数组,对Node数组的元素进行加锁。并增加红黑树(单向链表转红黑树)降低查询的时间复杂度。
初始化数组
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;
for (Node<K,V>[] tab = table;;) {//1
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();//2
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//3
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//4
break; // no lock when adding to empty bin
}
。。。。。。。。。。。。。。
- 当出现线程竞争时不断自旋
- 当数组为空时,初始化数组。
- 获取哈希值对应数组下标的链表的第一个节点
- 将欲插入数据封装为Node后通过cas原子操作放入链表,若操作成功跳出循环,操作失败说明存在线程竞争
initTable
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)//1
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//2
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];//3
table = tab = nt;
sc = n - (n >>> 2);//4
}
} finally {
sizeCtl = sc;//5
}
break;
}
}
return tab;
}
- sizeCtl<0说明此时有另一个线程正在初始化数组,让出CPU时间片
- 通过 cas 操作,将 sizeCtl 替换为-1,获取初始化数组的资格。
- 初始化数组,默认长度16,或在构造函数传入。
- 计算下次扩容大小,n>>>2的意思是若n为16,二进制位10000,右移2位,得到100,10进制为4,则sc为12。
- 将下次扩容大小记录到sizeCtl中。
分片计数
putVal方法执行完成以后,会通过addCount来增加ConcurrentHashMap中的元素个数。
元素的个数是通过一个数组CounterCell[]来存储,数组中元素记录的个数的总和就是ConcurrentHashMap元素的个数。
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1
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))) {//2
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();//3
}
。。。。
}
- 如果counterCells为空,尝试通过cas操作对ConcurrentHashMap元素的个数进行原子累加,若没有线程竞争的情况下,采用baseCount来记录元素的个数,若cas操作失败,则通过CounterCell来记录。
- 当满足一下条件时,需要调用fullAddCount进行counterCells的初始化和扩容:计数表counterCells为空;从计数表中随机取一个下标的数据为空;通过cas操作累加计数表中随机下标的值失败了。
- 统计 ConcurrentHashMap 元素个数,将counterCells中每一个元素的计数,和baseCount进行累加,获取总计数。
fullAddCount是对counterCells进行初始化、扩容、计数等。
在自旋操作中,如果计数器counterCells不为空随机在某个下标中添加一个CounterCell(x)(x是需要添加map元素的个数)或在原有的counterCells元素上累加;当线程竞争较大,counterCells长度不够时,则对counterCells进行扩容,在原有的长度上二进制左移1位(例原本长度是2,二进制10,左移1位是100,十进制4)
若计数器counterCells为空,则初始化长度为2的counterCells。
private final void fullAddCount(long x, boolean wasUncontended) {
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 r = new CounterCell(x); // Optimistic create
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) {
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;
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = ThreadLocalRandom.advanceProbe(h);
}
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;
}
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
、