【java基礎之ConcurrentHashMap原始碼分析】
概述:
ConcurrentHashMap這個類在java.lang.current包中,這個包中的類都是執行緒安全的。ConcurrentHashMap底層儲存資料的結構與1.8的HashMap是一樣的,都是陣列+連結串列(或紅黑樹)的結構。在日常的開發中,我們最長用到的鍵值對儲存結構的是HashMap,但是我們知道,這個類是非執行緒安全的,在高併發的場景下,在進行put操作的時候有可能進入死迴圈從而使伺服器的cpu使用率達到100%;sun公司因此也給出了與之對應的執行緒安全的類。在jdk1.5以前,使用的是HashTable,這個類為了保證執行緒安全,在每個類中都添加了synchronized關鍵字,而想而知在高併發的情景下相率是非常低下的。為了解決HashTable效率低下的問題,官網在jdk1.5後推出了ConcurrentHashMap來替代飽受詬病的HashTable。jdk1.5後ConcurrentHashMap使用了分段鎖的技術。在整個陣列中被分為多個segment,每次get,put,remove操作時就鎖住目標元素所在的segment中,因此segment與segment之前是可以併發操作的,上述就是jdk1.5後實現執行緒安全的大致思想。但是,從描述中可以看出一個問題,就是如果出現比較機端的情況,所有的資料都集中在一個segment中的話,在併發的情況下相當於鎖住了全表,這種情況下其實是和HashTable的效率出不多的,但總體來說相較於HashTable,效率還是有了很大的提升。jdk1.8後,ConcurrentHashMap摒棄了segment的思想,轉而使用cas+synchronized組合的方式來實現併發下的執行緒安全的,這種實現方式比1.5的效率又有了比較大的提升。
定義變數
1、ziseCtr:該變數主要是用來控制陣列的初始化和擴容的,預設值為0,可以概括一下4種狀態:
1.1、sizeCtr=0:預設值;
1.2、sizeCtr=-1:表示Map正在初始化中;
1.3、sizeCtr=-N:表示正在有N-1個執行緒進行擴容操作;
1.4、sizeCtr>0: 未初始化則表示初始化Map的大小,已初始化則表示下次進行擴容操作的閾值;
2、table:用於儲存連結串列或紅黑數的陣列,初始值為null,在第一次進行put操作的時候進行初始化,預設值為16;
3、nextTable:在擴容時新生成的陣列,其大小為當前table的2倍,用於存放table轉移過來的值;
4、Node:該類儲存資料的核心,以key-value形式來儲存;
5、ForwardingNode:這是一個特殊Node節點,僅在進行擴容時用作佔位符,表示當前位置已被移動或者為null,該node節點的hash值為-1;
原始碼部分
/** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * * <p>The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ 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) { //key和value不能為空,為空則直接返回異常 if (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; K fk; V fv; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else if (onlyIfAbsent && fh == hash && // check first node ((fk = f.key) == key || fk != null && key.equals(fk)) && (fv = f.val) != null) return fv; else { V oldVal = null; 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); 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; } } else if (f instanceof ReservationNode) throw new IllegalStateException("Recursive update"); } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }