-
map.put() 方法解析
//map.put 方法 // final V putVal(K key, V value, boolean onlyIfAbsent) {} public V put(K key, V value) { return putVal(key, value, false);//put方法實現 } // putVal() 方法 final V putVal(K key, V value, boolean onlyIfAbsent) { // key 和 value 不能爲null if (key == null || value == null) throw new NullPointerException(); // spread() 方法 : return (h ^ (h >>> 16)) & HASH_BITS; // 高16位與低16位異或運算 例: 211221 = 211222^3 // 然後和 HASH_BITS 位與運算 例: ① 211221 = 211221 & 2147483647 // ② 0 = 2147483648 & 2147483647 int hash = spread(key.hashCode());// 異或 與 運算得到的hash值 int binCount = 0;// 初始化定義binCount for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable();//執行初始化操作 初始化大小爲16 //通過哈希計算出一個表中的位置因爲n是數組的長度,所以(n-1)&hash肯定不會出現數組越界 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 } // MOVED(-1) 值是 -1 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f);// 當前Map在擴容,先協助擴容,在更新值。 else { V oldVal = null; // sysnchronized(線程安全)直接操作內存,對比lock性能兼容優越 // sysnchronized鎖住的是括號中的對象,並不是代碼塊,如果對象操作衝突就會進入等待 synchronized (f) { if (tabAt(tab, i) == f) { // fh 是 f 的 hash 值,f 是在內存中做了一個對比之後的值 // 當前hash存儲位置已經存在key值 執行覆蓋或者末尾添加操作 if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; // 判斷是否存在重複的key 更新oldVal 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; } } } // 判斷是否是treeBin 操作 執行紅黑樹的add操作 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) { // 鏈表存儲長度大於8 執行紅黑樹add操作 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } // 統計節點個數,如果在8個以上的話,則會調用treeifyBin方法,來嘗試轉化爲樹,或者是擴容 // 引用地址:http://www.cnblogs.com/zerotomax/p/8687425.html addCount(1L, binCount); return null; } // initTable() 初始化操作 private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; //第一次put的時候,table還沒被初始化,進入while while ((tab = table) == null || tab.length == 0) { //sizeCtl初始值爲0,當小於0的時候表示在別的線程在初始化表或擴展表 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //SIZECTL:表示當前對象的內存偏移量,sc表示期望值,-1表示要替換的值,設定爲-1表示要 //初始化表了 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 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]; table = tab = nt; // 位運算符 >>> : 位補零右移運算 // 負載係數 16 - (16 >>> 2) = 12 = 16 * 0.75 sc = n - (n >>> 2); } } finally { // 最終一定執行 sizeCtl 的賦值 sizeCtl = sc; } break; } } return tab; }
-
map.get() 方法解析
// map.get() 方法 public V get(Object key) { Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; int h = spread(key.hashCode());// 得到key的hash值 if ((tab = table) != null && (n = tab.length) > 0 && // (n - 1) & h) 當前數組的長度與hash值 與運算 // 計算當前key 需要存儲的位置 如果不是null (e = tabAt(tab, (n - 1) & h)) != null) { // 如果存儲的位置已經存在key if ((eh = e.hash) == h) { // 如果key值是重複的 執行覆蓋方法 if ((ek = e.key) == key || (ek != null && key.equals(ek))) return e.val; } //hash值爲負值表示正在擴容,這個時候查的是ForwardingNode的find方法來定位到nextTable來 //eh=-1,說明該節點是一個ForwardingNode,正在遷移,此時調用ForwardingNode的find方法去 //nextTable裏找。 //eh=-2,說明該節點是一個TreeBin,此時調用TreeBin的find方法遍歷紅黑樹,由於紅黑樹有可能正 //在旋轉變色,所以find裏會有讀寫鎖。 //eh>=0,說明該節點下掛的是一個鏈表,直接遍歷該鏈表即可。 else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; while ((e = e.next) != null) {// 以上條件都不滿足 繼續循環查找 if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; }
-
map.remove() 方法解析
// map.remove() 方法 public V remove(Object key) { return replaceNode(key, null, null); } // replaceNode() 方法 final V replaceNode(Object key, V value, Object cv) { int hash = spread(key.hashCode());// 取當前key的hash值 //循環操作 concurrentHashMap 當其他線程在執行擴容 //操作時 可以協助處理, 處理完之後再來執行當前方法,提高擴容時copy效率 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; // 判斷數據是否爲null 或者通過計算得到的存放地址key爲null直接結束循環 if (tab == null || (n = tab.length) == 0 || (f = tabAt(tab, i = (n - 1) & hash)) == null) break; else if ((fh = f.hash) == MOVED)// 判斷是否有其他線程在執行擴容操作 tab = helpTransfer(tab, f);// 協助 執行 數據 Transfer 提高效率 else { V oldVal = null; boolean validated = false;// 定義validated 爲 false // synchronized 鎖括號中的對象 防止多個線程同時都操作這個對象 排隊等候 synchronized (f) { // 判斷內存中是否存在 f if (tabAt(tab, i) == f) { // fh 當前存儲位置存在數據 並且數據書鏈表存儲方式 if (fh >= 0) { validated = true; //循環查找當前鏈表數據 for (Node<K,V> e = f, pred = null;;) { K ek; // 判斷是否是需要remove的數據 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { V ev = e.val; if (cv == null || cv == ev || (ev != null && cv.equals(ev))) { oldVal = ev;// 保留oldVal 返回 if (value != null) e.val = value; // 判斷當前鏈表存儲位置 的上一個node是否是null else if (pred != null) // 移動上一個node.next = e.next // remove e pred.next = e.next; else // 設置tab的當前i位置爲e.next setTabAt(tab, i, e.next); } break;//結束循環 } pred = e;//以上條件不滿足 繼續循環 // 如果e.next 是null 結束循環 if ((e = e.next) == null) break; } } // 判斷是否是紅黑樹節點類型 執行紅黑樹操作方法 else if (f instanceof TreeBin) { validated = true; TreeBin<K,V> t = (TreeBin<K,V>)f;//轉換爲treeBin類型操作 TreeNode<K,V> r, p; // 判斷樹的根節點不爲null 並且 根據hash和key得到的數據不爲null if ((r = t.root) != null && (p = r.findTreeNode(hash, key, null)) != null) { // 定義pv V pv = p.val; if (cv == null || cv == pv || (pv != null && cv.equals(pv))) { oldVal = pv;//保留oldVal 返回使用 if (value != null)// value 傳值一般都是null p.val = value; else if (t.removeTreeNode(p))//執行樹的remove操作 // 從新設置數組 setTabAt(tab, i, untreeify(t.first)); } } } } } // 剛開始以爲validated會造成死循環 經過分析這裏本身就是一個循環,在validated爲 //false到這裏的時候會繼續循環,查找當前需要remove的Node,爲null的情況下直接返回 if (validated) { if (oldVal != null) { if (value == null) addCount(-1L, -1); return oldVal; } break; } } } return null;//沒有匹配返回null }
-
map.clear() 方法解析
// map.clear() 方法 public void clear() { long delta = 0L; // negative number of deletions int i = 0; Node<K,V>[] tab = table; // 循環執行數據的clear while (tab != null && i < tab.length) { int fh; Node<K,V> f = tabAt(tab, i); if (f == null) ++i; else if ((fh = f.hash) == MOVED) {// 判斷是否有線程在執行擴容操作 tab = helpTransfer(tab, f);// 執行協助 i = 0; // restart } else { synchronized (f) { if (tabAt(tab, i) == f) { Node<K,V> p = (fh >= 0 ? f : (f instanceof TreeBin) ? ((TreeBin<K,V>)f).first : null); while (p != null) { --delta; p = p.next; } setTabAt(tab, i++, null); } } } } if (delta != 0L) addCount(delta, -1); }
hash table雖然性能上不如ConcurrentHashMap,但並不能完全被取代,兩者的迭代器的一致性不同的,hash table的迭代器是強一致性的,而concurrenthashmap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。
下面是大白話的解釋:
- Hashtable的任何操作都會把整個表鎖住,是阻塞的。好處是總能獲取最實時的更新,比如說線程A調用putAll寫入大量數據,期間線程B調用get,線程B就會被阻塞,直到線程A完成putAll,因此線程B肯定能獲取到線程A寫入的完整數據。壞處是所有調用都要排隊,效率較低。
- ConcurrentHashMap 是設計爲非阻塞的。在更新時會局部鎖住某部分數據,但不會把整個表都鎖住。同步讀取操作則是完全非阻塞的。好處是在保證合理的同步前提下,效率很高。壞處 是嚴格來說讀取操作不能保證反映最近的更新。例如線程A調用putAll寫入大量數據,期間線程B調用get,則只能get到目前爲止已經順利插入的部分 數據。
選擇哪一個,是在性能與數據一致性之間權衡。ConcurrentHashMap適用於追求性能的場景,大多數線程都只做insert/delete操作,對讀取數據的一致性要求較低。
---------------------
原文:https://blog.csdn.net/programmer_at/article/details/79715177