併發編程(十二):ConcurrentHashMap源碼分析

一,ConcurrentHashMap概述

1,ConcurrentHashMap

    * ConcurrentHashMap HashMap 原理基本一致,就是在 HashMap 的基礎上增加了鎖處理,支持併發操作,在實現上比 HashMap 更復雜點。先比較與 JDK7,JDK8在實現上,修改原來通過 Segment 進行加鎖的方式改爲通過 Node 進行加鎖,同時在鏈表方面,如果鏈表長度超過閾值,則轉換爲紅黑樹;紅黑樹部分沒有分析,因爲不會。。。

2,類圖

3,常量及常用API

3.1,常量

// 最大允許長度
private static final int MAXIMUM_CAPACITY = 1 << 30;

// 默認長度
private static final int DEFAULT_CAPACITY = 16;

// 最大數組長度-轉數組使用
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 擴容因子
private static final float LOAD_FACTOR = 0.75f;

// 鏈表 -> 紅黑樹轉換閾值
static final int TREEIFY_THRESHOLD = 8;

// 紅黑水 -> 鏈表轉換閾值
static final int UNTREEIFY_THRESHOLD = 6;

// 最小樹化容量,如果需要轉換紅黑樹並且容量小於該值,則先擴容
static final int MIN_TREEIFY_CAPACITY = 64;

// 擴容操作中,單個線程的最小步進
// 數據遷移通過分段遷移,由多線程協調執行,最小段數量爲16,則如果長度爲16,由一個線程進行擴容
private static final int MIN_TRANSFER_STRIDE = 16;

// 擴容操作使用
private static int RESIZE_STAMP_BITS = 16;

// 擴容操作使用,進行sizeCtl高低位移動,進行擴容線程數判斷
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

//最大擴容線程數量
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

// ForwardingNode的hash值,ForwardingNode是一種臨時節點,在擴進行中才會出現,並且它不存儲實際的數據,ForwardingNode繼承自Node,默認hash初始化爲-1
static final int MOVED     = -1;

// 紅黑樹的HASH值
static final int TREEBIN   = -2;

// ReservationNode的hash值,ReservationNode是一個保留節點,就是個佔位符
static final int RESERVED  = -3;

// 用於和負數hash值進行 & 運算,將其轉化爲正數(絕對值不相等)
static final int HASH_BITS = 0x7fffffff;

// CPU的核心數
static final int NCPU = Runtime.getRuntime().availableProcessors();

3.2,變量

// 鏈表
transient volatile Node<K,V>[] table;

// 擴容時候的新鏈表
private transient volatile Node<K,V>[] nextTable;

// 分段計數,記錄
private transient volatile CounterCell[] counterCells;

/*
 * 非常重要的一個屬性,源碼中的英文翻譯,直譯過來是下面的四行文字的意思
 *       sizeCtl = -1,表示有線程正在進行真正的初始化操作
 *       sizeCtl = -(1 + nThreads),表示有nThreads個線程正在進行擴容操作
 *       sizeCtl > 0,表示接下來的真正的初始化操作中使用的容量,或者初始化/擴容完成後的閾值
 *       sizeCtl = 0,默認值,此時在真正的初始化操作中使用默認容量
 */
private transient volatile int sizeCtl;

// 擴容任務的起始下標
private transient volatile int transferIndex;

// CAS自旋鎖標誌位,用於初始化,或者counterCells擴容時
private transient volatile int cellsBusy;

// 計數器基本值,主要在沒有碰到多線程競爭時使用,需要通過CAS進行更新
private transient volatile long baseCount;

/* Contended類 */
// 當前統計數量
volatile long value;

3.3,常用API

/* 初始化部分 */
public ConcurrentHashMap();
public ConcurrentHashMap(int initialCapacity);
public ConcurrentHashMap(Map<? extends K, ? extends V> m);

/* 寫數據部分 */
public V put(K key, V value);
public void putAll(Map<? extends K, ? extends V> m);

/* 讀數據部分 */
public V get(Object key);

/* 移除數據部分 */
public V remove(Object key);

/* 獲取長度 */
public int size();

3.4,核心原子方法

// 獲取 i 索引處對象
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);
}

// 把 i 索引處爲 c 的對象替換爲 v
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
									Node<K,V> c, Node<K,V> v) {
	return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

// 把 i 索引處對象設置爲 v
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
	U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

四,源碼分析

1,初始化源碼分析

1.1,ConcurrentHashMap(int initialCapacity)

    * ConcurrentHashMap(int initialCapacity)

public ConcurrentHashMap(int initialCapacity) {
	// 校驗參數合法性
	if (initialCapacity < 0)
		throw new IllegalArgumentException();
	// 大於最大值,取最大值
	// 合法數據,進行數據重處理,向上取整爲2的整數次方
	// 向上取整數倍爲了再下標計算時更分散,該部分後續有機會在 HashMap 中分析
	int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
			   MAXIMUM_CAPACITY :
			   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
	this.sizeCtl = cap;
}

    * tableSizeFor(int c)

private static final int tableSizeFor(int c) {
	// 通過下列一系列或等於操作後,獲取到的值肯定是2的整數次方
	int n = c - 1;
	n |= n >>> 1;
	n |= n >>> 2;
	n |= n >>> 4;
	n |= n >>> 8;
	n |= n >>> 16;
	return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

1.2,ConcurrentHashMap(Map<? extends K, ? extends V> m)

    * ConcurrentHashMap(Map<? extends K, ? extends V> m)

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
	// 初始化長度爲默認長度,即16
	this.sizeCtl = DEFAULT_CAPACITY;
	// 調用 putAll 方法進行數據添加
	// putAll 內部循環調用 putVal 方法,後續分析
	putAll(m);
}

2,put()源碼分析

2.1,put() 總述

    * put(K key, V value)

public V put(K key, V value) {
	// 直接調用 putVal() 進行數據添加
	return putVal(key, value, false);
}

    * putVal(K key, V value, boolean onlyIfAbsent)

final V putVal(K key, V value, boolean onlyIfAbsent) {
	if (key == null || value == null) throw new NullPointerException();
	// 獲取hash值
	int hash = spread(key.hashCode());
	int binCount = 0;
	for (Node<K,V>[] tab = table;;) {
		Node<K,V> f; int n, i, fh;
		// 第一階段:初始化階段
		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, null)))
				break;                   // no lock when adding to empty bin
		}
		// 第二階段:擴容階段
		// 第一次觸發擴容在統計數量,超過閾值後觸發
		// 此處表示擴容期間如果存在數據添加,則會增加一道線程去輔助擴容
		else if ((fh = f.hash) == MOVED)
			tab = helpTransfer(tab, f);
		// 第四階段:鏈表 + 紅黑樹操作
		// 該部分在第四階段分析時候,會單掕出來再分析
		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, 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;
}

    * spread(int h):獲取key值對應的hash值,此處注意不是下標

static final int spread(int h) {
	// 此處用當前key哈希值的高16位與低16位按位異或之後,在於魔數進行與運算
	// 步驟的目的是儘量使hash值分散,之後與(length - 1)進行與運算,儘量保證數據能均勻分散
	// 這也是爲什麼長度要是2的整數次方,比如16的二進制是100000,減1就是11111,再進行與運算,儘量保證分散
	return (h ^ (h >>> 16)) & HASH_BITS;
}

static final int HASH_BITS = 0x7fffffff;

2.2,put() 第一階段:初始化階段

    * initTable()

private final Node<K,V>[] initTable() {
	Node<K,V>[] tab; int sc;
	// 此處通過自旋,保證初始化成功
	while ((tab = table) == null || tab.length == 0) {
		// sizeCtl 在初始化時候已經複製爲長度,即tab.length
		// sizeCtl 表示正在初始化或者正在擴容
		if ((sc = sizeCtl) < 0)
			Thread.yield();
		// 初始化時候,將 SIZECTL 的值修改爲 -1,該狀態比較重要,後續多處會涉及
		else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
			try {
				// 再對 table 進行判斷,確定爲空時候進行初始化
				if ((tab = table) == null || tab.length == 0) {
					// 長度爲0,取默認長度,即16
					int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
					// 初始化 Node 數組,並賦值給 table
					@SuppressWarnings("unchecked")
					Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
					table = tab = nt;
					// 此處的結果是 3/4 * n,即0.75 * n,也就是擴容因子
					// 後續如果數量超過 sc 的數量,說明需要擴容
					sc = n - (n >>> 2);
				}
			} finally {
				sizeCtl = sc;
			}
			break;
		}
	}
	return tab;
}

2.3,put() 第二階段(1):數據統計階段

    * ConcurrentHashMap 數據統計採用分段統計。使用 CounterCell 數組存儲每一段的數據數量,再獲取總數據時,遍歷求和。

    * addCount(long x, int check)

// x 表示增加數量
// check 表示鏈表數量
// 總數量增加後可能會觸發擴容,即正式觸發第三階段
private final void addCount(long x, int check) {
	CounterCell[] as; long b, s;
	// 首先對 CounterCell[] 進行判空,如果 CounterCell[] 爲空,則依舊嘗試通過CAS修改 baseCount 的值,由該值記錄元素個數
	// 如果CAS失敗,說明存在線程競爭,則通過 CounterCell 記錄個數
	// CounterCell[] 不爲空,直接通過 CounterCell 記錄個數
	if ((as = counterCells) != null ||
		!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
		CounterCell a; long v; int m;
		// 是否衝突,默認表示沒有衝突
		boolean uncontended = true;
		// 計數表爲空,直接調用下面方法
		// 計數表元素爲空,直接調用下面方法; 技術表元素不爲空,則隨機獲取一個有效元素作爲 CounterCell 進行CAS處理
		// ThreadLocalRandom.getProbe() 隨機數,和 m 即計數表長度進行與運算後,取一個有效的下標
		// 通過CAS修改 CounterCell 的值,如果修改失敗,說明存在線程競爭,調用下面方法
		if (as == null || (m = as.length - 1) < 0 ||
			(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
			!(uncontended =
			  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
			// if條件走完後,沒有遞增成功,則進行遞增操作
			fullAddCount(x, uncontended);
			return;
		}
		// 鏈表長度小於等於1,不考慮擴容
		// 鏈表轉紅黑樹需要滿足兩個條件:鏈表長度大於8,數組長度大於64
		// 如果鏈表長度大於8,但是數組長度沒有超過64,則先進行擴容操作
		if (check <= 1)
			return;
		// 統計 ConcurrentHashMap 的元素個數
		s = sumCount();
	}
	// >=0,表示該位置存在元素
	if (check >= 0) {
		Node<K,V>[] tab, nt; int n, sc;
		// s表示當前元素總數,sizeCtl表示擴容閾值,大於表示需要擴容
		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();
		}
	}
}

    * fullAddCount(long x, boolean wasUncontended):增加數量,該部分不涉及擴容

// x : 增加數量
// wasUncontended:是否存在衝突
private final void fullAddCount(long x, boolean wasUncontended) {
	int h;
	// 獲取線程的 probe 值,值如果沒有初始化,則進行初始化,並重置未衝突表示爲true
	if ((h = ThreadLocalRandom.getProbe()) == 0) {
		ThreadLocalRandom.localInit(); 
		h = ThreadLocalRandom.getProbe();
		wasUncontended = true;
	}
	boolean collide = false;
	for (;;) {
		CounterCell[] as; CounterCell a; int n; long v;
		// CounterCell[] 數組不爲空,已經存在有效的 CounterCell 元素
		if ((as = counterCells) != null && (n = as.length) > 0) {
			// 獲取下標,找到對應的 CounterCell 進行遞增操作
			if ((a = as[(n - 1) & h]) == null) {
				// 爲0表示不存在線程進行遞增處理
				if (cellsBusy == 0) {
					// 初始化一個 CounterCell,並賦初值爲遞增值
					CounterCell r = new CounterCell(x);
					// 將 cellsBusy 狀態改爲正在進行遞增處理
					if (cellsBusy == 0 &&
						U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
						boolean created = false;
						try {
							// 將初始化的 CounterCell 添加到數組中
							CounterCell[] rs; int m, j;
							if ((rs = counterCells) != null &&
								(m = rs.length) > 0 &&
								rs[j = (m - 1) & h] == null) {
								rs[j] = r;
								created = true;
							}
						} finally {
							cellsBusy = 0;
						}
						// 創建失敗,說明存在競爭,則繼續自旋處理
						if (created)
							break;
						continue;
					}
				}
				collide = false;
			}
			// 後續操作首先說明 CounterCell 不爲空
			// 貌似沒有應用,先不用管
			else if (!wasUncontended)
				wasUncontended = true;
			// 通過CAS遞增 value 值,修改成功則執行完成
			else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
				break;
			// 此處說明競爭下衝突,獲取已經超過CPU數量,當前循環失敗
			else if (counterCells != as || n >= NCPU)
				collide = false;
			else if (!collide)
				collide = true;
			// 對 CounterCell 數組進行擴容,保證高併發下的操作
			else if (cellsBusy == 0 &&
					 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
				try {
					if (counterCells == as) {
						CounterCell[] rs = new CounterCell[n << 1];
						for (int i = 0; i < n; ++i)
							rs[i] = as[i];
						counterCells = rs;
					}
				} finally {
					cellsBusy = 0;
				}
				collide = false;
				continue;
			}
			h = ThreadLocalRandom.advanceProbe(h);
		}
		// cellsBusy 爲0,表示沒有線程在做初始化,修改值爲1
		// CounterCell[] 數組爲空,但是不爲null,如果不爲空會走上一個if,如果爲null會走下一個if
		else if (cellsBusy == 0 && counterCells == as &&
				 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
			// 此處添加初始化標誌
			boolean init = false;
			try {
				// 第一個if已經進行了as複製,所以此處==,不存在競爭情況下基本成立
				if (counterCells == as) {
					// 初始化CounterCell[]數組,數組長度初始化爲2
					CounterCell[] rs = new CounterCell[2];
					// 將遞增的數量,複製給其中之一,
					rs[h & 1] = new CounterCell(x);
					counterCells = rs;
					// 初始化爲true
					init = true;
				}
			// 遞增完成後,重置值
			} finally {
				cellsBusy = 0;
			}
			if (init)
				break;
		}
		// 上面兩條都不符合,則直接對baseCount遞增,CAS成功後返回
		else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
			break; 
		// 否則進入自旋
	}
}

    * sumCount():獲取 ConcurrentHashMap 元素總數量

final long sumCount() {
	CounterCell[] as = counterCells; CounterCell a;
	// 如果不存在線程競爭,則 counterCells 爲空,則數據存儲在 baseCount 中
	long sum = baseCount;
	// counterCells 不爲空,存在線程競爭,則遍歷獲取總數
	if (as != null) {
		for (int i = 0; i < as.length; ++i) {
			if ((a = as[i]) != null)
				sum += a.value;
		}
	}
	return sum;
}

2.4,put() 第二階段(2):擴容階段

    * 第三階段擴容擴容階段分爲兩部分內容,首先是 addCount() 部分的擴容操作,因爲數量遞增引起的擴容,算是正式開啓擴容階段;第二部分是 putVal() 時候的分支條件進行輔助擴容,是在已經有線程進行擴容的基礎上,如果還存在線程進行數據操作,則進行輔助擴容。擴容部分應該是難度最大的一部分

    * addCount(long x, int check):再探擴容觸發條件

private final void addCount(long x, int check) {
	CounterCell[] as; long b, s;
	... // 中間部分省略,上面已經分析
	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);
			// sizeCtl小於0,說明有其他線程正在進行擴容處理,則此時sc已經經過擴容戳處理
			if (sc < 0) {
				// 這五個條件有一個爲true,說明當前線程不再進行擴容處理
				// sc >>> RESIZE_STAMP_SHIFT != rs,如果不存在線程競爭,則此時獲取到的值應該相等,不等於說明存在競爭
				// sc == rs + 1 : 擴容已經結束
				// sc == rs + MAX_RESIZERS:幫助線程已經最大值
				// (nt = nextTable) == null:擴容已經結束
				// transferIndex <= 0:表示所有的擴容任務已經被領完,沒有剩餘的hash桶來給自己線程做擴容
				if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
					sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
					transferIndex <= 0)
					break;
				// 當前線程嘗試幫助擴容,如果成功,則進行擴容
				// sc + 1:表示擴容線程數量遞增
				if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
					transfer(tab, nt);
			}
			// sizeCtl > 0,直接進行擴容處理
			// rs << RESIZE_STAMP_SHIFT:將sc設置爲一個負數,+2表示有一個線程在執行擴容任務
			// 計算完resizeStamp()再繼續看,結果集爲 00000000 00000000 10000000 00011011
			// 則sc替換後的值應該爲 10000000 00011011 00000000 00000010,此時低16位就表示並行線程數,後續表示並行線程數也是對該值加1
			else if (U.compareAndSwapInt(this, SIZECTL, sc,
										 (rs << RESIZE_STAMP_SHIFT) + 2))
				transfer(tab, null);
			s = sumCount();
		}
	}
}

    * resizeStamp(int n):擴容戳詳細分析

// n:表長度
static final int resizeStamp(int n) {
	// Integer.numberOfLeadingZeros 這個方法是返回無符號整數 n 最高位非 0 位前面的 0 的個數
	// 比如 16 的32位是 00000000 00000000 00000000 00010000,則值爲27
	// RESIZE_STAMP_BITS爲16,就是1右移15位,及 00000000 00000000 10000000 00000000
	// 最終結果就是 00000000 00000000 10000000 00011011
	return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

    * transfer(Node<K,V>[] tab, Node<K,V>[] nextTab):擴容,擴容是核心內容,重點在於數據遷移及鏈表部分處理。ConcurrentHashMap 內容通過多線程來進行協調擴容,把 Node 數組作爲一個多線程共享的共享隊列,然後通過指針來劃分每個線程負責的區間,每個線程區間通過線程逆向遍歷來進行擴容。

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
	int n = tab.length, stride;
	// 一個CPU一次最少默認處理16個hash位置,即步進
	// 大於則計算,小於則取默認16,由一個CPU執行
	if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
		stride = MIN_TRANSFER_STRIDE;
	// nextTab 表示需要擴容的表,如果爲空,則初始化,注意初始化長度爲當前長度的二倍
	if (nextTab == null) {            
		try {
			@SuppressWarnings("unchecked")
			Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
			nextTab = nt;
		} catch (Throwable ex) {      
			sizeCtl = Integer.MAX_VALUE;
			return;
		}
		nextTable = nextTab;
		transferIndex = n;
	}
	// 新數組長度
	int nextn = nextTab.length;
	// 構建一個 ForwardingNode 節點,表示一個正在遷移的Node,hash值爲-1(MOVED),表示構建一個標示位
	ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
	// 下標推進標識,爲true,說明需要推進,爲false,說明未處理完成
	// 推進表示推進一個 stride 的區間
	boolean advance = true;
	// 是否已經完成,默認false,即對當前 stride 區間數據遷移完成
	boolean finishing = false; 
	// i表示當前處理的槽位序號,bound表示需要處理的槽位邊界
	for (int i = 0, bound = 0;;) {
		Node<K,V> f; int fh;
		// 該循環通過CAS不斷嘗試爲當前線程分配任務,即一個 stride
		// 線程對 stride 區間處理,從尾部到頭部倒敘處理
		// 如果當前線程已經被分配過 stride 區域,那麼通過--i遞減後繼續進行節點遷移
		while (advance) {
			int nextIndex, nextBound;
			// bound 表示 stride 邊界的頭節點
			// i 表示 stride 邊界的尾結點
			// 因爲是倒敘進行處理,所以在任務處理中, i 肯定大於 bound
			if (--i >= bound || finishing)
				advance = false;
			// transferIndex 表示擴容前數組長度,賦值給 nextIndex
			else if ((nextIndex = transferIndex) <= 0) {
				i = -1;
				advance = false;
			}
			// 上一個if分支已經給 nextIndex 賦值
			// 假如數組長度目前爲16,則 nextIndex爲16,nextBound爲0,當前任務區間爲 [0, 15]
			else if (U.compareAndSwapInt
					 (this, TRANSFERINDEX, nextIndex,
					  nextBound = (nextIndex > stride ?
								   nextIndex - stride : 0))) {
				bound = nextBound;
				i = nextIndex - 1;
				advance = false;
			}
		}
		// i < 0 說明已經遍歷完成,也就是當前線程已經處理完成所有的 stride
		if (i < 0 || i >= n || i + n >= nextn) {
			int sc;
			// 總任務已經完成
			if (finishing) {
				// 對相關參數進行重置後退出
				nextTable = null;
				table = nextTab;
				// 更新擴容閾值
				sizeCtl = (n << 1) - (n >>> 1);
				return;
			}
			// 此處表示總任務沒有完成,只是當前線程已經處理完成任務
			// 之前有提過多線程同時處理時對 sizeCtl 進行高低位操作後用來計數,執行完成後,對應計數遞減
			if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
				// 在擴容時,會對 SIZECTL 進行基本計算賦值 (rs << RESIZE_STAMP_SHIFT) + 2)
				// 所以在對最後一個線程進行處理時,必然存在條件成立
				// 如果條件不成立,說明已經全部執行完成,直接退出
				if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
					return;
				// 任務執行完成,擴容結束沒更新finishing變量
				finishing = advance = true;
				// 再次循環檢查一次
				i = n; 
			}
		}
		// 如果i位置元素空,則放入剛纔初始化的 ForwardingNode 空節點,標識該位置正在遷移
		else if ((f = tabAt(tab, i)) == null)
			advance = casTabAt(tab, i, null, fwd);
		// 表示位置已經完成了遷移
		else if ((fh = f.hash) == MOVED)
			advance = true; 
		else {
			// 對當前遍歷的hash位置進行遷移,加鎖處理,這也是JDK8不同於JDK7的一個主要地方,對Node加鎖
			// 再分析這部分前先對鏈表遷移的高低鏈原理進行分析
			synchronized (f) {
				...
			}
		}
	}
}

    * 鏈表遷移的高低鏈原理分析

// 首先明確一個概念,Map的下標計算是通過 hash & length 獲取到的下標,而不是單純的 hash 表示
// 所以在每一個鏈表表示的節點上,雖然衆多元素下標值衝突,掛載同一個下標點下,但是 hash 值基本上是不一致的
// 在這種情況下,鏈表遷移的hash鏈表示,其實是對 hash 值二進制下,與原數組長度進行與操作後進行非0判斷
// 該位置數字如果爲0,則表示低鏈,該位置數組如果爲1,則表示高鏈
// 低鏈說明該鏈上的元素在當前索引不變
// 高鏈表示該鏈上的元素會隨着擴容同時增加一個擴容長度的下標,比如擴容前長度爲16,下標爲4,則擴容後下標爲20
// 計算方式如下,比如對於hash值4和20的元素,在16原始長度下
4	= 00000000 00000000 00000000 00000100
15	= 00000000 00000000 00000000 00001111	&
---------------------------------------------
	  00000000 00000000 00000000 00000100 = 4 (下標爲4)
	  
20	= 00000000 00000000 00000000 00010100
15	= 00000000 00000000 00000000 00001111	&
---------------------------------------------
	  00000000 00000000 00000000 00000100 = 4 (下標爲4)
	  
// 如上,在16長度下,對於4和20,下標都爲4,此時如果對16進行擴容,擴容到32
4	= 00000000 00000000 00000000 00000100
16	= 00000000 00000000 00000000 00010000	&
---------------------------------------------
	  00000000 00000000 00000000 00000100 = 4 (下標爲4)
	  
20	= 00000000 00000000 00000000 00010100
31	= 00000000 00000000 00000000 00010000	&
---------------------------------------------
	  00000000 00000000 00000000 00010100 = 20 (下標爲20 = 4 + 16)
	  
// 這個例子就是說明,數組庫容後,(新數組長度 - 1)最高非0位爲倒數第五位,
// 則對4和20的第五位進行判斷,4爲0,則在低鏈。20爲1,則在高鏈
// 數據遷移時,低鏈位置不動,高鏈位置 = 原位置 + 擴容長度
// 繼續往上加衝突索引,到36,52,68...也是遵循這個規則

    * 繼續分析槽點元素遷移

// 對當前遍歷的hash位置進行遷移,加鎖處理,這也是JDK8不同於JDK7的一個主要地方,對Node加鎖
// 再分析這部分前先對鏈表遷移的高低鏈原理進行分析
synchronized (f) {
	// 判斷當前必然有值
	if (tabAt(tab, i) == f) {
		Node<K,V> ln, hn;
		// hash 值大於0,說明當前節點表示單元素或者鏈表
		if (fh >= 0) {
			// n 爲原數組長度,下標位置計算,是 fh & (n - 1)
			// 此處直接 & n,爲了獲取元素 hash 值對應長度1位置的數字是否爲0
			int runBit = fh & n;
			Node<K,V> lastRun = f;
			// 因爲 Node 爲單向鏈表,此處直接遍歷到尾部,獲取到尾部節點
			for (Node<K,V> p = f.next; p != null; p = p.next) {
				int b = p.hash & n;
				if (b != runBit) {
					runBit = b;
					lastRun = p;
				}
			}
			// 先把尾部節點進行高低鏈歸屬處理
			// 爲0表示在低鏈
			// 爲1表示在高鏈
			if (runBit == 0) {
				ln = lastRun; // low
				hn = null;
			}
			else {
				hn = lastRun; // high
				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;
				if ((ph & n) == 0)
					ln = new Node<K,V>(ph, pk, pv, ln);
				else
					hn = new Node<K,V>(ph, pk, pv, hn);
			}
			// 處理完成後,低鏈位置不動,高鏈位置遞增一個擴容位置
			setTabAt(nextTab, i, ln);
			setTabAt(nextTab, i + n, hn);
			// 把舊錶中的hash桶中放置標識位節點,表示已經被處理
			setTabAt(tab, i, fwd);
			advance = true;
		}
		// 表示紅黑樹,紅黑樹部分真TM不會,以後會了再補充吧 TODO
		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;
		}
	}
}

2.5,put() 第三階段:輔助擴容階段

    * helpTransfer(Node<K,V>[] tab, Node<K,V> f)

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
	Node<K,V>[] nextTab; int sc;
	// f instanceof ForwardingNode 表示當前節點正在擴容,則輔助擴容
	if (tab != null && (f instanceof ForwardingNode) &&
		(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
		// 生成擴容戳
		int rs = resizeStamp(tab.length);
		// 說明擴容還爲完成,參與擴容
		while (nextTab == nextTable && table == tab &&
			   (sc = sizeCtl) < 0) {
			// (sc >>> RESIZE_STAMP_SHIFT) != rs:說明擴容已經結束了
			// sc == rs + 1:說明擴容已經結束 這兩個條件根據擴容戳的高低位轉換後判斷可以得出
			// sc == rs + MAX_RESIZERS:已經到達最大擴容數,不再參與
			// transferIndex <= 0:所有hash位置已經分配完畢
			if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
				sc == rs + MAX_RESIZERS || transferIndex <= 0)
				break;
			// 參與擴容,擴容線程數+1,進行庫容處理
			if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
				transfer(tab, nextTab);
				break;
			}
		}
		return nextTab;
	}
	return table;
}

2.6,put() 第四階段:鏈表到紅黑樹構造階段

    * 繼續回到 putVal() 方法中,單掕出來查看鏈表到紅黑樹構造的代碼,紅黑樹不分析,因爲不會

else {
	V oldVal = null;
	// 對Node進行加鎖,同步處理
	synchronized (f) {
		// 再次判斷下標位置是否是f節點
		if (tabAt(tab, i) == f) {
			// 頭節點值大於0,說明是鏈表
			if (fh >= 0) {
				// 記錄鏈表長度
				binCount = 1;
				// 遍歷鏈表
				for (Node<K,V> e = f;; ++binCount) {
					K ek;
					// 判斷是否是相同hash,hash相同進行value替換
					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;
					}
				}
			}
			// 頭節點置小於0,說明是紅黑樹
			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;
				}
			}
		}
	}
	// 此處是鏈表長度大於8,進行紅黑樹處理
	if (binCount != 0) {
		if (binCount >= TREEIFY_THRESHOLD)
			treeifyBin(tab, i);
		if (oldVal != null)
			return oldVal;
		break;
	}
}

    * treeifyBin(Node<K,V>[] tab, int index):鏈表轉樹處理,此處多跟一步,爲了說明在鏈表長度大於閾值後,還要判斷數組長度是否大於閾值,不大於後,直接擴容

private final void treeifyBin(Node<K,V>[] tab, int index) {
	Node<K,V> b; int n, sc;
	if (tab != null) {
		// 判斷數組長度是否大於閾值64,如果不大於,首先觸發擴容
		if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
			tryPresize(n << 1);
		// 條件全部滿足,觸發樹
		else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
			synchronized (b) {
				if (tabAt(tab, index) == b) {
					TreeNode<K,V> hd = null, tl = null;
					for (Node<K,V> e = b; e != null; e = e.next) {
						TreeNode<K,V> p =
							new TreeNode<K,V>(e.hash, e.key, e.val,
											  null, null);
						if ((p.prev = tl) == null)
							hd = p;
						else
							tl.next = p;
						tl = p;
					}
					setTabAt(tab, index, new TreeBin<K,V>(hd));
				}
			}
		}
	}
}

3,get()源碼分析

    * get(Object key)

public V get(Object key) {
	Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
	// 獲取key對應的hash值
	int h = spread(key.hashCode());
	// 數組不爲空,且數組對應元素不爲空,繼續尋找
	if ((tab = table) != null && (n = tab.length) > 0 &&
		(e = tabAt(tab, (n - 1) & h)) != null) {
		// hash值匹配,直接返回值
		if ((eh = e.hash) == h) {
			if ((ek = e.key) == key || (ek != null && key.equals(ek)))
				return e.val;
		}
		// hash值爲0,表示爲紅黑數,不分析。。。
		else if (eh < 0)
			return (p = e.find(h, key)) != null ? p.val : null;
		// 走到此處說明爲鏈表。遍歷樹不對hash,獲取值
		while ((e = e.next) != null) {
			if (e.hash == h &&
				((ek = e.key) == key || (ek != null && key.equals(ek))))
				return e.val;
		}
	}
	return null;
}

4,remove()源碼分析

    * remove(Object key)

public V remove(Object key) {
	// 移除節點,並返回當前節點,不存在返回null
	return replaceNode(key, null, null);
}

    * replaceNode(Object key, V value, Object cv)

final V replaceNode(Object key, V value, Object cv) {
	// 獲取哈希值
	int hash = spread(key.hashCode());
	for (Node<K,V>[] tab = table;;) {
		Node<K,V> f; int n, i, fh;
		// 數組爲空,或者下標所在元素不存在,直接返回
		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);
		else {
			// 節點元素不爲空,判斷移除
			V oldVal = null;
			boolean validated = false;
			synchronized (f) {
				// 二次校驗
				if (tabAt(tab, i) == f) {
					// 當前下標元素存在,爲單個元素或者鏈表
					if (fh >= 0) {
						validated = true;
						// 遍歷當前節點的鏈表進行處理
						for (Node<K,V> e = f, pred = null;;) {
							K ek;
							// 此部分表示找到對應的hash值
							// 然後移除該節點,將後續節點填充
							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;
									if (value != null)
										e.val = value;
									else if (pred != null)
										pred.next = e.next;
									else
										setTabAt(tab, i, e.next);
								}
								break;
							}
							pred = e;
							if ((e = e.next) == null)
								break;
						}
					}
					// 紅黑樹,不分析。。。
					else if (f instanceof TreeBin) {
						validated = true;
						TreeBin<K,V> t = (TreeBin<K,V>)f;
						TreeNode<K,V> r, p;
						if ((r = t.root) != null &&
							(p = r.findTreeNode(hash, key, null)) != null) {
							V pv = p.val;
							if (cv == null || cv == pv ||
								(pv != null && cv.equals(pv))) {
								oldVal = pv;
								if (value != null)
									p.val = value;
								else if (t.removeTreeNode(p))
									setTabAt(tab, i, untreeify(t.first));
							}
						}
					}
				}
			}
			if (validated) {
				// 返回oldValue
				if (oldVal != null) {
					if (value == null)
						addCount(-1L, -1);
					return oldVal;
				}
				break;
			}
		}
	}
	return null;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章