- ConcurrentHashMap 是java提供的一個線程安全的鍵值對集合,其採用分段鎖的模式提高了其併發度。
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;
// 無限循環,除非break
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,如果該節點的鏈表存儲被轉化爲樹節點存儲,那麼這裏就會不相等。
if (tabAt(tab, i) == f) {
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);
}
}
- 通過以上的源碼分析:我們可以看到jdk1.8 實現的ConcurrentHashMap 底層數據結構是數組+鏈表+紅黑樹,通過CAS和synchronized 來控制多線程下併發訪問。通過對node節點的加鎖那麼隻影響當前加鎖的鏈表數組。
- 通過basecount+CounterCell[] 數組來計算size。basecount在無競爭的情況下就足夠使用了。