【collection】2.java容器之HashMap&LinkedHashMap&Hashtable2

ConcurrentHashMap

put操作

final V putVal(K key, V value, boolean onlyIfAbsent) {
	if (key == null || value == null) throw new NullPointerException();
	// 本質上和hashmap沒有什麼差別,都是把hashcode進行對半異或,這樣就可以用一半的位數,集合了32位長度的信息
	int hash = spread(key.hashCode());
	int binCount = 0;
	Node<K,V>[] tab = table;
	// 這裏循環的目的是cas的重試操作
	for (;;) {
		// 指向待插入元素應當插入的位置
		Node<K,V> f;
		// 元素f對應的哈希值
		int fh;
		// 當前hash表數組的長度容量
		int n;
		int i;
		// 如果哈希數組還未初始化,或者容量無效,則需要初始化一個哈希數組
		if (tab == null || (n = tab.length) == 0) {
			tab = initTable();
			// 這裏n -1 & hash 是經典取餘操作,參考之前hashmap
		} else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
			// 如果當前位置是空的,那麼就可以通過cas設置對應的值
			Node<K, V> newNode = new Node<K,V>(hash, key, value, null);
			// 用一次 CAS 操作將這個新值放入其中即可,這個 put 操作差不多就結束了,可以拉到最後面了
			// 如果 CAS 失敗,那就是有併發操作,進到下一個循環就好了
			if (casTabAt(tab, i, null, newNode)) {
				// 插入完成,跳出循環,如果更新失敗,重新循環進入
				break;                   // no lock when adding to empty bin
			}
		} else if ((fh = f.hash) == MOVED) {
			/*
			 * 如果待插入元素所在的哈希槽上已經有別的結點存在,且該結點類型爲MOVED
			 * 說明當前哈希數組正在擴容中,此時,可以嘗試加速擴容過程
			 */
			tab = helpTransfer(tab, f);
		} else {
			V oldVal = null;
			// 這裏避免併發,對f節點的引用進行上鎖
			synchronized (f) {
				// 如果tab[i]==f,則代表當前待插入狀態仍然可信
				if (tabAt(tab, i) == f) {
					// fh > 0 標識不是在擴容,是正常節點
					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) {
				// 哈希槽(鏈)上的元素數量增加到TREEIFY_THRESHOLD後,這些元素進入波動期,即將從鏈表轉換爲紅黑樹
				if (binCount >= TREEIFY_THRESHOLD) {
					// 注意,這裏也只是鎖了這一個節點,注意,這裏不一定一定是轉換爲紅黑樹
					// 如果整個tab長度是小於64的話,這裏會選擇自動擴容,如果已經超過64了,才考慮轉換紅黑樹
					treeifyBin(tab, i);
				}
				if (oldVal != null)
					return oldVal;
				break;
			}
		}
	}
	addCount(1L, binCount);
	return null;
}

get和remove操作

略,就是根據索引找節點

擴容

核心方法就是transfer

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
	int n = tab.length;
	// stride 在單核下直接等於 n,多核模式下爲 (n>>>3)/NCPU,最小值是 16
	//   將這 n 個任務分爲多個任務包,每個任務包有 stride 個任務
	int stride = (NCPU > 1) ? (n >>> 3) / NCPU : n;
	if (stride < MIN_TRANSFER_STRIDE) {
		stride = MIN_TRANSFER_STRIDE; // subdivide range
	}
	if (nextTab == null) {            // initiating
		try {
			// 直接擴大一倍
			@SuppressWarnings("unchecked")
			Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
			nextTab = nt;
		} catch (Throwable ex) {      // try to cope with OOME
			sizeCtl = Integer.MAX_VALUE;
			return;
		}
		nextTable = nextTab;
		transferIndex = n;
	}
	int nextn = nextTab.length;

	// ForwardingNode 翻譯過來就是正在被遷移的 Node
	// 這個構造方法會生成一個Node,key、value 和 next 都爲 null,關鍵是 hash 爲 MOVED
	// 後面我們會看到,原數組中位置 i 處的節點完成遷移工作後,
	//    就會將位置 i 處設置爲這個 ForwardingNode,用來告訴其他線程該位置已經處理過了
	//    所以它其實相當於是一個標誌。, 這個在put的時候會判斷節點hash值,用來判斷是否需要協助擴容
	ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
	// advance 指的是做完了一個位置的遷移工作,可以準備做下一個位置的了
	boolean advance = true;
	boolean finishing = false; // to ensure sweep before committing nextTab
	// 循環所有的節點位置,判斷是否需要進行遷移
	for (int i = 0, bound = 0;;) {
		Node<K,V> f; int fh;
		while (advance) {
			int nextIndex, nextBound;
			if (--i >= bound || finishing) {
				advance = false;
			} else if ((nextIndex = transferIndex) <= 0) {
				i = -1;
				advance = false;
			} else if (U.compareAndSwapInt
					 (this, TRANSFERINDEX, nextIndex,
					  nextBound = (nextIndex > stride ?
								   nextIndex - stride : 0))) {
				bound = nextBound;
				i = nextIndex - 1;
				advance = false;
			}
		}
		if (i < 0 || i >= n || i + n >= nextn) {
			int sc;
			if (finishing) {
				nextTable = null;
				table = nextTab;
				sizeCtl = (n << 1) - (n >>> 1);
				return;
			}
			if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
				if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
					return;
				finishing = advance = true;
				i = n; // recheck before commit
			}
		}
		else if ((f = tabAt(tab, i)) == null)
			advance = casTabAt(tab, i, null, fwd);
		else if ((fh = f.hash) == MOVED)
			advance = true; // already processed
		else {
			// 真正的開始數據遷移,先對f節點上鎖,f是tab中的一個位置
			synchronized (f) {
				// 保證數據正確性沒有發生變化
				if (tabAt(tab, i) == f) {
					Node<K,V> ln, hn;
					// 頭節點的 hash 大於 0,說明是鏈表的 Node 節點
					if (fh >= 0) {
						// 下面這一塊和 Java7 中的 ConcurrentHashMap 遷移是差不多的,
						// 需要將鏈表一分爲二,
						// 找到原鏈表中的 lastRun,然後 lastRun 及其之後的節點是一起進行遷移的
						// lastRun 之前的節點需要進行克隆,然後分到兩個鏈表中
						int runBit = fh & n;
						Node<K,V> lastRun = f;
						// 循環鏈表,獲取到尾節點
						for (Node<K,V> p = f.next; p != null; p = p.next) {
							int b = p.hash & n;
							if (b != runBit) {
								runBit = b;
								lastRun = p;
							}
						}
						if (runBit == 0) {
							ln = lastRun;
							hn = null;
						} else {
							hn = lastRun;
							ln = null;
						}
						// 從頭循環到尾部
						for (Node<K,V> p = f; p != lastRun; p = p.next) {
							int ph = p.hash;
							K pk = p.key;
							V pv = p.val;
							// ph是這個節點的hash值&n如果爲0,說明再n-1部分,位置沒變,還是放入之前的位置
							if ((ph & n) == 0) {
								ln = new Node<K,V>(ph, pk, pv, ln);
							} else {
								// 不爲0,說明再n-1上面的位置,位置變了,那麼就重新設置位置
								hn = new Node<K,V>(ph, pk, pv, hn);
							}
						}
						// 其中的一個鏈表放在新數組的位置 i
						setTabAt(nextTab, i, ln);
						// 把head放到數組i+n的位置
						setTabAt(nextTab, i + n, hn);
						// 將原數組該位置處設置爲 fwd,代表該位置已經處理完畢,
						//    其他線程一旦看到該位置的 hash 值爲 MOVED,就不會進行遷移了
						setTabAt(tab, i, fwd);
						advance = true;
					} else if (f instanceof TreeBin) {
						// 紅黑樹的遷移
						TreeBin<K,V> t = (TreeBin<K,V>)f;
						TreeNode<K,V> lo = null, loTail = null;
						TreeNode<K,V> hi = null, hiTail = null;
						int lc = 0, hc = 0;
						for (Node<K,V> e = t.first; e != null; e = e.next) {
							int h = e.hash;
							TreeNode<K,V> p = new TreeNode<K,V>
								(h, e.key, e.val, null, null);
							if ((h & n) == 0) {
								if ((p.prev = loTail) == null)
									lo = p;
								else
									loTail.next = p;
								loTail = p;
								++lc;
							}
							else {
								if ((p.prev = hiTail) == null)
									hi = p;
								else
									hiTail.next = p;
								hiTail = p;
								++hc;
							}
						}
						ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
							(hc != 0) ? new TreeBin<K,V>(lo) : t;
						hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
							(lc != 0) ? new TreeBin<K,V>(hi) : t;
						setTabAt(nextTab, i, ln);
						setTabAt(nextTab, i + n, hn);
						setTabAt(tab, i, fwd);
						advance = true;
					}
				}
			}
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章