專欄系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162
本文目標:
- 理解ConcurrentHashMap為什么線程安全;ConcurrentHashMap的具體細節還需要進一步研究
目錄
- ConcurrentHashMap介紹
- JDK7的分段鎖實現
- JDK8的synchronized + CAS實現
- put方法
ConcurrentHashMap介紹
百度AI介紹如下:
JDK7的分段鎖實現
在 JDK7 中,ConcurrentHashMap 使用“分段鎖”機制實現線程安全,數據結構可以看成是”Segment數組+HashEntry數組+鏈表”,一個 ConcurrentHashMap 實例中包含若干個 Segment 實例組成的數組,每個 Segment 實例又包含由若干個桶,每個桶中都是由若干個 HashEntry 對象鏈接起來的鏈表。
Segment 類繼承 ReentrantLock 類,鎖的粒度為其中一個Segment,而不是整體。
JDK8的synchronized + CAS實現
每個桶可能是鏈表
結構或者紅黑樹
結構,鎖針對桶的頭節點加,鎖粒度小
put方法
定位Node數組位置使用CAS操作定位,真正進行插入操作的時候會使用synchronized
關鍵字加鎖頭部
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {// key, value 都不能為nullif (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 如果Node數組是空,則進行初始化;初始化是CAS操作if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 數組位置節點為null,則CAS方式進行添加Node到數組位置if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)// 如果數組位置節點正在遷移,則幫助遷移tab = helpTransfer(tab, f);else {// 沒有遷移,且數組位置不是空,則進行聊表或者紅黑樹的插入操作,可能涉及到鏈表轉紅黑樹V oldVal = null;// 直接用 synchronized 鎖住 鏈表或者紅黑樹的頭部synchronized (f) {if (tabAt(tab, i) == f) {// 鏈表遍歷判斷,替換老值,或者進行尾插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)))) {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;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;
}
put完成后addCount(1L, binCount);
會進行數量統計和擴容判斷操作,也是CAS操作
/*** Adds to count, and if table is too small and not already* resizing, initiates transfer. If already resizing, helps* perform transfer if work is available. Rechecks occupancy* after a transfer to see if another resize is already needed* because resizings are lagging additions.** @param x the count to add* @param check if <0, don't check resize, if <= 1 only check if uncontended*/
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)) {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))) {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();}}
}