- ConcurrentHashMap 是java提供的一個線程安全的鍵值對集合,1.7採用分段鎖的模式,1.8採用CAS+synchronized 保證安全。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
private static final int DEFAULT_CAPACITY = 16;
// 併發級別
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 存儲數組
transient volatile Node<K,V>[] table;
// 控制多線程數組的初始化操作。 volite 保證了對其讀寫操作的可見性,原子性。禁止重排序
// 初始化完成後,則控制調整大小的閾值。
private transient volatile int sizeCtl;
// 存儲方法
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key value 都不能爲null.這個和HashMap不一樣。
if (key == null || value == null) throw new NullPointerException();
// 對key 的哈希碼做一些處理。
int hash = spread(key.hashCode());
int binCount = 0;
// 循環嘗試插入節點直到插入成功。
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 初始化數組爲null或長度爲0 ,則調用初始化方法進行。
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 獲取i位置上的元素,爲null則繼續。
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//通過CAS操作插入節點
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果已存在元素且元素的hash值爲-1 則說明在擴容遷移中
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//對已存在的節點鏈表的首節點進行加鎖,進行操作。這裏已經產生hash碰撞
synchronized (f) {
// 判斷i位置的節點是否等於f,等於說明其他線程沒有刪除該節點操作。
// 這裏使用了雙重檢查,防止操作時該節點被刪除或者移動了位置等。
// 如果不等於,則f被刪除或者移動到了其他位置,則循環繼續嘗試添加。
if (tabAt(tab, i) == f) {
// 當前節點的hash值大於等於0
if (fh >= 0) {
binCount = 1;
//循環查找
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果鏈表中已經存在當前要插入的key,則會覆蓋值。
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;
//樹節點插入
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// binCount 大於0則代表是hash碰撞產生
if (binCount != 0) {
//大於等於8則將該節點對應的鏈表轉化爲紅黑樹存儲
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
//如果不是覆蓋,則返回null.覆蓋原來節點的值則返回原來存儲的值。
if (oldVal != null)
return oldVal;
break;
}
}
}
//這裏會設置BaseCount的值,用來計算size大小。bincount代表插入位置鏈表的長度或者插入節點爲樹節點
// 1L 代表basecount加1.
addCount(1L, binCount);
return null;
}
// 初始化table 方法
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// 如果sizeCtl 小於0,說明應該有其他線程在操作,則當前線程調用yield 方法
//暫停當前線程執行,當前線程由運行狀態變爲可執行狀態
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 否則嘗試使用CAS設置sizeCtl 的值爲-1 SIZECTL 標識sizeCtl 變量相對於this對象的偏移量,sc是原來的值0,比較當前對象位置上sizeCtl 的存儲值是否等於0,等於則設置sizeCtl 值爲-1.即先比較後交換。比較原來的值和當前次對象位置上存儲的值是否相等,相等代表沒有其他線程修改過,則可以安全的更新值。CAS是比較常用的搭配volite 關鍵字實現原子性對變量的更新操作。
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
// 默認長度16,構建長度16的node節點數組。
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
//初始化完成,退出循環。
break;
}
}
// 返回tab
return tab;
}
// 獲取數組i位置的元素。unsafe 操作是一個線程安全的操作。
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);
}
// 這裏我們來看下如何計算size 的大小的。
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// counterCells 用來記錄當競爭激烈時,增加的size大小。
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
//counterCells 不爲空或者CAS設置baseCount 失敗。baseCount 記錄競爭不激烈的情況下size的大小。統計size時,baseCount加上counterCells數組記錄的長度的和
CounterCell a; long v; int m;
boolean uncontended = true;
// counterCells 等於null 或者長度小於1 或者當前線程的探針值與當前長度減一 位置上的元素爲null,不爲null則CAS 嘗試將該元素值更新,更新失敗則進入。這是避免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))) {
//
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
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);
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();
}
}
}
/**
* Initialize Thread fields for the current thread. Called only
* when Thread.threadLocalRandomProbe is zero, indicating that a
* thread local seed value needs to be generated. Note that even
* though the initialization is purely thread-local, we need to
* rely on (static) atomic generators to initialize the values.
*初始化當前線程的線程字段。僅在Thread.threadLocalRandomProbe爲零時調用,表示需要生成線
* 程本地種子值。請注意,即使*初始化純粹是線程局部的,我們也需要依靠(靜態)原子生成器來初始化值
*/
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 線程沒有本地初始化種子值的時候,探針值爲0
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;
// counterCells 數組不等於空且長度大於0,則說明不是第一次,不需要初始化該數組。
if ((as = counterCells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
// 該線程是第一次進入該數組進行計數統計
if (cellsBusy == 0) {
// 嘗試創建新的單元格即 CounterCell 當cellsBusy 等於0
// Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
// cellsBusy 等於0且CAS更新值爲1 成功
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
// j 當前線程應該存放到位置 再次檢查rs數組,雙重檢查機制
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;
}
// CAS 設置失敗
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// CAS 嘗試更新a的屬性值,a就是找到的元素對象
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// counterCells 被改變或者其原來的長度大於等於處理器個數
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)) {
// cellsBusy 等於0且嘗試更新值爲1 成功
try {
// counterCells 沒有改變
if (counterCells == as) {// Expand table unless stale
CounterCell[] rs = new CounterCell[n << 1];
//構建長度爲原來2倍的新數組並將原來數組copy到新數組
for (int i = 0; i < n; ++i)
rs[i] = as[i];
// counterCells 變更爲新數組,長度爲原來的2倍
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 {
// 初始化 counterCells 默認長度爲2
// 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
}
}
// 這是一個靜態常量內部類。只有一個volatile 修飾的value屬性,保證value的CAS更新操作具有可見性。
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
}
- 通過以上的源碼分析:我們可以看到jdk1.8 實現的ConcurrentHashMap 底層數據結構是數組+鏈表+紅黑樹,通過CAS和synchronized 來控制多線程下併發訪問。通過對鏈表或者紅黑樹首節點的加鎖提高了併發度。
- 通過basecount+CounterCell[] 數組來計算size。basecount在無競爭的情況下就足夠使用了。
- 如果我們通過CAS 設置basecount 的值失敗,則會嘗試使用CounterCell 數組,CounterCell數組的每個位置上的元素記錄了一個線程更改增加的元素個數。位置的計算由線程探針位移與數組長度-1就是相當於取模對CounterCell數組長度 。
- CounterCell 數組初始化長度爲2,在嘗試插入或者更新失敗後,會嘗試擴展數組即擴展爲原來的2倍。
- 通過CounterCell 數組的設計可以避免線程記錄長度時產生競爭,當然這個相當於hash的計算還是可能產生碰撞的,但是通過cas競爭時會導致更新失敗,則繼續不斷嘗試則可能最大程度避免計數失敗。
- CounterCell + basecount的計數方式,即適用於競爭不大的情況,也適應了多線程競爭激烈情況下的併發度考慮。優化了多線程操作時的計數。