本文基於JDK1.8
ThreadLocal是啥?用來幹啥?
public class Thread implements Runnable {
//執行緒內部區域性變數
ThreadLocal.ThreadLocalMap threadLocals = null;
//子執行緒繼承父執行緒的變數
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
Java官方對ThreadLocal類的定義如下:
- ThreadLocal類用於提供執行緒內部的區域性變數。
- 這種變數在多執行緒環境下訪問(通過
get
和set
方法訪問)時能保證各個執行緒的變數相對獨立於其他執行緒內的變數。 ThreadLocal
例項通常來說都是private static
型別的,用於關聯執行緒和執行緒上下文。
ThreadLocal的作用:
ThreadLocal
的作用是提供執行緒內的區域性變數,不同的執行緒之間不會相互干擾,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或元件之間一些公共變數的傳遞的複雜度。
ThreadLocal的簡單使用
/**
* 官方demo,測試ThreadLocal的用法
*
* @author Summerday
*/
public class ThreadLocalTest {
public static void main(String[] args) {
// main Thread
incrementSameThreadId();
new Thread(ThreadLocalTest::incrementSameThreadId).start();
new Thread(ThreadLocalTest::incrementSameThreadId).start();
}
private static void incrementSameThreadId() {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread() + "_" + i + ",threadId:" + ThreadLocalId.get());
}
} finally {
// 使用後請清除
ThreadLocalId.remove();
}
}
}
class ThreadLocalId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
// remove currentid
public static void remove() {
threadId.remove();
}
}
//輸出
Thread[main,5,main]_0,threadId:0
Thread[main,5,main]_1,threadId:0
Thread[main,5,main]_2,threadId:0
Thread[Thread-0,5,main]_0,threadId:1
Thread[Thread-0,5,main]_1,threadId:1
Thread[Thread-0,5,main]_2,threadId:1
Thread[Thread-1,5,main]_0,threadId:2
Thread[Thread-1,5,main]_1,threadId:2
Thread[Thread-1,5,main]_2,threadId:2
ThreadLocal的實現思路?
- Thread類中有一個型別為
ThreadLocal.ThreadLocalMap
的例項變數threadLocals,意味著每個執行緒都有一個自己的ThreadLocalMap。 - 可以簡單地將key視作ThreadLocal,value為程式碼中放入的值(實際上key並不是ThreadLocal本身,而是它的一個弱引用)。
- 每個執行緒在往某個ThreadLocal裡塞值的時候,都會往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,從而實現了執行緒隔離。
ThreadLocal常見方法原始碼分析
ThreadLocal.set(T value)
public void set(T value) {
//獲取到當前的執行緒物件
Thread t = Thread.currentThread();
//進而獲取此執行緒物件中維護的ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
//如果ThreadLocalMap存在,則以當前的ThreadLocal為key,value作為值設定entry
if (map != null)
map.set(this, value);
else
//呼叫createMap進行ThreadLocalMap物件的初始化,並將此實體作為第一個值
createMap(t, value);
}
//建立一個與執行緒t關聯的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal.get()
public T get() {
//獲取到當前的執行緒物件
Thread t = Thread.currentThread();
//進而獲取此執行緒物件中維護的ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
//如果此map存在
if (map != null) {
//以當前的ThreadLocal 為 key,呼叫getEntry獲取對應的儲存實體e
ThreadLocalMap.Entry e = map.getEntry(this);
//找到對應的儲存實體 e
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map不存在,呼叫setInitialValue進行初始化
return setInitialValue();
}
private T setInitialValue() {
//呼叫initialValue獲取初始化的值
T value = initialValue();
//獲取當前執行緒物件
Thread t = Thread.currentThread();
//獲取此執行緒物件中維護的ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
//如果此map存在
if (map != null)
//存在則呼叫map.set設定此實體entry
map.set(this, value);
else
////呼叫createMap進行ThreadLocalMap物件的初始化,並將此實體作為第一個值
createMap(t, value);
return value;
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//以當前ThreadLocal為key刪除對應的實體entry
m.remove(this);
}
可以發現,ThreadLocal的這些方法,都是通過當前執行緒找到對應的map,其實都是對其維護的ThreadLocalMap這個物件進行的操作,這些細節後面探討。
ThreadLocalMap原始碼分析
ThreadLocalMap結構分析
ThreadLocalMap的底層實現是一個定製的自定義HashMap:
Entry[] table
:底層是一個Entry型別的陣列,必要時需要進行擴容,陣列的長度必須是2的n次冪,為了在Hash時效率更高:當n為2的n此冪時,hashCode % len
與hashCode & (len -1)
效果相同,但位運算效率更高。int threshold
:下次擴容時的閾值,threshold = len * 2 / 3
。當size >= threshold
時,遍歷table
並刪除key
為null
的元素,如果刪除後size >= threshold * 3 / 4
時,需要對table
進行擴容。- Entry:是雜湊表儲存的核心元素,Entry繼承了弱引用。
- ThreadLocal<?> k:當前儲存的ThreadLocal例項。
- object value:當前ThreadLocal對應儲存的值。
Entry繼承了弱引用,這樣會導致Entry[]table
中的每個資料可能會有三種狀態:
- entry不為null,且key不為null,正常資料。
- entry不為null,但key為null,表示過期資料。
- entry == null。
static class ThreadLocalMap {
/**
* 實體Entry在此hash map中是繼承弱引用 WeakReference,
* 使用ThreadLocal 作為 key 鍵.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 當前 ThreadLocal 對應儲存的值value. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量大小 16 -- 必須是2的n次方.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 底層雜湊表 table, 必要時需要進行擴容.
* 底層雜湊表 table.length 長度必須是2的n次方.
*/
private Entry[] table;
/**
* 實際儲存鍵值對元素個數 entries.
*/
private int size = 0;
/**
* 下一次擴容時的閾值
*/
private int threshold; // 預設為 0
/**
* 設定觸發擴容時的閾值 threshold
* 閾值 threshold = 底層雜湊表table的長度 len * 2 / 3
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 獲取該位置i對應的下一個位置index
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 獲取該位置i對應的上一個位置index
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
}
ThreadLocalMap的Hash演算法
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//...省略get set remove等方法
static class ThreadLocalMap {
//2的冪 len % c = len & (c - 1)
private static final int INITIAL_CAPACITY = 16;
//ThreadLocalMaps是延遲載入的,在有entry實體存放時,才初始化建立一次。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 雜湊演算法 , 利用陣列容量2的冪次的特性,位運算快速得到位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
ThreadLocalMap使用線性探測法來解決雜湊衝突,而Entry[]table其實在邏輯上也就是可以想象成一個環,這也可以看出nextIndex和prevIndex兩個方法,是獲取環形意義上的下一個位置。
每當建立一個ThreadLocal物件,nextHashCode就會增長0x61c88647
。0x61c88647
這個值非常特殊,被稱作斐波那契數,它能使hash分佈更加均勻,線性探測時就能更快探測到下一個臨近可用的slot,從而保證效率。
ThreadLocalMap.set()
https://snailclimb.gitee.io/javaguide/#/docs/java/Multithread/ThreadLocal
private void set(ThreadLocal<?> key, Object value) {
//set方法需要考慮到hash碰撞,相對get方法比較複雜一些
Entry[] tab = table;
int len = tab.length;
//得到在table中的索引位置
int i = key.threadLocalHashCode & (len-1);
//迴圈遍歷
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
//獲取當前位置的ThreadLocal
ThreadLocal<?> k = e.get();
//如果key一致,則直接賦予新值【替換操作】,並退出
if (k == key) {
e.value = value;
return;
}
//當前位置的key為null【過期資料】,呼叫replaceStaleEntry方法,並退出
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//遍歷過程中,遇到entry == null 的情況【沒有資料衝突】,直接使用這個桶
tab[i] = new Entry(key, value);
int sz = ++size;
//呼叫cleanSomeSlots啟發式清理操作,清理雜湊陣列中Entry的key過期資料
//清理完成後,未清理到任何資料,且size超過了閾值,進行rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//rehash不等於resize!
rehash();
}
private void rehash() {
// 進行一輪探測式清理【全量清理】
expungeStaleEntries();
// 清理完成後,如果滿足size >= threshold - threshold / 4,執行擴容邏輯
// threshold預設是 len * 2/3 size >= len / 2 預設執行擴容
if (size >= threshold - threshold / 4)
resize();
}
ThreadLocalMap.resize()擴容
//將表的容量加倍
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
//逐一遍歷舊的雜湊表的每一個entry,重新分配至新的雜湊表中
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // 清除無效entry的value值,幫助GC回收
} else {
// 線性探測來存放Entry
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//重新計算閾值
setThreshold(newLen);
size = count;
table = newTab;
}
- 擴容後的tab大小為oldLen * 2。
- 遍歷老陣列,重新計算hash位置,放到新的tab陣列中。
- 如果出現hash衝突,則往後找到最近的entry為null的槽位。
- 遍歷完成之後oldTab中所有的資料置如新的tab。
- 重新計算tab下次擴容的閾值。
ThreadLocalMap.get()
private Entry getEntry(ThreadLocal<?> key) {
//通過key計算出雜湊表中的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果key一致,命中直接返回
if (e != null && e.get() == key)
return e;
else
//不一致,呼叫方法繼續找
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//遇到無效的slot,執行一次探測式的清理
if (k == null)
expungeStaleEntry(i);
else
//index後移
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocalMap.remove()
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 計算對應threalocal的儲存位置
int i = key.threadLocalHashCode & (len-1);
// 迴圈遍歷table對應該位置的實體,查詢對應的threadLocal
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
// 如果key threadLocal一致,則證明找到對應的threadLocal
if (e.get() == key) {
// 執行清除操作
e.clear();
// 清除此位置的實體
expungeStaleEntry(i);
// 結束
return;
}
}
}
ThreadLocalMap.replaceStaleEntry()
在set方法中,迴圈尋找桶時,找到過期的key,將會呼叫該方法,replaceStaleEntry方法提供了替換過期資料的功能:
// 在執行set操作時,獲取對應的key,並替換過期的entry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 往前找到table中第一個過期的實體的下標
// 清理整個table範圍,避免因為垃圾回收帶來的連續增長雜湊的危險
// 記錄slotToExpunge 開始探測式清理過期資料的開始下標
int slotToExpunge = staleSlot;
//向前遍歷查詢第一個過期的實體下標
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 向後遍歷查詢 key一致的ThreadLocal或 key為null 的
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果找到key,【將它跟新的過期資料交換】
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 兩個索引重合,表明在【整個掃描過程中】前+後掃描 的時候並沒有找到過期的key
if (slotToExpunge == staleSlot)
//修改開始探測式清理過期資料的下標為當前迴圈的index
slotToExpunge = i;
// 從slotToExpunge開始做一次啟發式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 如果當前的slot已經無效,並且向前掃描過程中沒有無效slot,則更新slotToExpunge為當前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 最後key仍然沒有找到,則將要設定的新實體放置在原過期的實體對應的位置上
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 在探測過程中如果發現任何無效slot,則做一次清理(探測式清理+啟發式清理)
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
ThreadLocalMap過期key清理流程
探測式清理expungeStaleEntry
expungeStaleEntry方法將會遍歷雜湊陣列,從開始位置向後探測清理過期資料,將過期資料的Entry設定為null,沿途中碰到未過期的資料則將此資料rehash後重新在table中定位。
如果定位的位置已經有了資料,則會將未過期的資料放到最靠近此位置的Entry==null的桶中,使rehash後的Entry資料距離正確的桶的位置更近一些。
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 因為entry對應的ThreadLocal已經被回收,value設為null,顯式斷開強引用
tab[staleSlot].value = null;
// 顯式設定該entry為null,以便垃圾回收
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//向後遍歷
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//遇到k == null 的過期資料,清空該槽位
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// key沒有過期,重新計算當前key的下標位置是不是當前槽位的下標位置
int h = k.threadLocalHashCode & (len - 1);
// 如果不是,說明產生了hash衝突
if (h != i) {
tab[i] = null;
//以新計算出正確的槽位位置往後迭代,找到最近一個可以存放entry的位置.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//經過迭代之後,有hash衝突的資料的entry位置會更加靠近正確的位置,查詢效率更高。
return i;
}
啟發式清理cleanSomeSlots
在新增Entry或過期元素被清除時呼叫:
- 如果沒有過期資料,只要掃描logN次即可,這個演算法的時間複雜度為O(logN)。
- 如果有過期資料,需要將n置為table的長度len,做一次探測式清理,再從下一個空的slot開始繼續掃描。
// i對應的entry是非無效的
// n用於控制掃描次數
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//從i的下一個開始,因為i不是無效的
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
//擴大掃描因子
n = len;
removed = true;
//清理一個連續段
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocal的記憶體洩漏問題
我們需要明確ThreadLocalMap的結構,ThreadLocalMap中的儲存實體Entry使用ThreadLocal作為key,但這個Entry繼承弱引用WeakReference。
強引用與弱引用的區別
弱引用與強引用有啥區別?在這邊複習一下:引用型別有四種,強引用屬於第一檔,而弱引用屬於第三檔。
- 強引用:將一個物件賦值給一個引用變數,這個引用變數就是一個強引用,當一個物件被強引用物件引用時,處於可達狀態,就不會被垃圾回收機制回收。
- 弱引用:通過WeakReference實現,弱引用的生命週期很短,只要垃圾回收機制執行,不管JVM的記憶體空間是否足夠,總會回收該物件佔用的記憶體。
舉個例子:
A a = new A();
a = null;
由於a = null,一段時間之後,Java垃圾回收機制就會將a對應的記憶體空間回收。
B b = new B(a);
a = null;
當a被設定為null之後,GC卻並不會回收a的記憶體空間,原因在於:儘管a已經為null,但物件B仍然持有對a的強引用,所以這時a這塊記憶體就出現了記憶體洩漏,因為無法回收,也無法使用。
解決的辦法有兩種:
- 強行讓b為null,這樣對於物件a就再沒有強引用指向它。
- 讓a成為弱引用型別:
WeakReference w = new WeakReference(a);
,對於弱引用,GC是可以回收a原先分配的記憶體空間的。
ThreadLocal記憶體洩漏如何造成?
ThreadLocalMap
使用ThreadLocal
的弱引用作為key
,如果一個ThreadLocal
沒有外部強引用來引用它,那麼系統 GC 的時候,這個ThreadLocal
勢必會被回收,這樣一來,ThreadLocalMap
中就會出現key
為null
的Entry
,就沒有辦法訪問這些key
為null
的Entry
的value
。
如果當前執行緒再遲遲不結束的話,這些key
為null
的Entry
的value
就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠無法回收,造成記憶體洩漏。
ThreadLocalMap的設計中已經考慮到這種情況,也再get,set,remove等方法上做了預防:在呼叫之後都清除執行緒ThreadLocalMap所有key為null的value。
使用ThreadLocal發生記憶體洩漏的前提條件:
- ThreadLocal引用被設定為null,且後面沒有set,get,remove等操作。
- 執行緒一致執行,不停止。【執行緒池】
- 觸發了垃圾回收。【MinorGC或FullGC】
如何解決這個問題呢?
- ThreadLocal申明為
private static final
:private final儘量不讓他人修改變更引用,static類屬性,只有在程式結束的時候才會被回收。 - 每次使用完
ThreadLocal
,都呼叫它的remove()
方法,清除資料。
那為什麼要設計使用弱引用呢?強引用不香麼?
這個問題可以從如果使用強引用會引發什麼問題討論:
如果使用強引用,也就是普通的key-value形式定義儲存結構,實質上就是將節點的生命週期與執行緒強行繫結,只要執行緒沒有銷燬,節點在GC分析中一直處於可達狀態,無法被回收。
使用弱引用的好處在於,如果某個ThreadLocal已經沒有強引用可達,它就會被垃圾回收,ThreadLocalMap中對應的Entry也會失效,為ThreadLocalMap本身的垃圾清理提供了便利。
ThreadLocal可以解決哪些問題?
解決併發問題
使用ThreadLocal
代替synchronized
來保證執行緒安全。同步機制採用了“以時間換空間”的方式,而ThreadLoca
l採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。
Java7 的SimpleDateFormat不是執行緒安全的,可以通過ThreadLocal解決
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return format1.get().format(date);
}
}
解決資料儲存問題
ThreadLocal
為變數在每個執行緒中都建立了一個副本,所以每個執行緒可以訪問自己內部的副本變數,不同執行緒之間不會互相干擾。如一個Parameter
物件的資料需要在多個模組中使用,如果採用引數傳遞的方式,顯然會增加模組之間的耦合性。此時我們可以使用ThreadLocal
解決。
以下例子用於session的儲存
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
InheritableThreadLocal是啥?
InheritableThreadLocal主要用於子執行緒建立時,需要自動繼承父執行緒的ThreadLocal變數,方便必要資訊的進一步傳遞。
實現原理是子執行緒是通過在父執行緒中通過呼叫new Thread()
方法來建立子執行緒,Thread#init
方法在Thread
的構造方法中被呼叫。在init
方法中拷貝父執行緒資料到子執行緒中
執行緒初始化程式碼:
/**
* 初始化一個執行緒.
* 此函式有兩處呼叫,
* 1、上面的 init(),不傳AccessControlContext,inheritThreadLocals=true
* 2、傳遞AccessControlContext,inheritThreadLocals=false
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//......(其他程式碼)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//......(其他程式碼)
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 構建一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
* 該函式只被 createInheritedMap() 呼叫.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 儲存ThreadLocal
table = new Entry[len];
// 逐一複製 parentMap 的記錄
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}