本文結構
ThreadLocal
簡介 (簡要說明ThreadLocal
的作用)ThreadLocal實
現原理(說明ThreadLocal
的常用方法和原理)ThreadLocalMap
的實現 (說明核心資料結構ThreadLocalMap
的實現)
ThreadLocal簡介
先貼一段官方的文件解釋
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
大意是ThreadLocal類提供了一個執行緒的本地變數,每一個執行緒持有一個這個變數的副本並且每個執行緒讀取get()
到的值是不一樣的,可以通過set()
方法設定這個值;
在某些情況下使用ThreadLocal可以避免共享資源的競爭,同時與不影響執行緒的隔離性。
通過threadLocal.set方法將物件例項儲存在每個執行緒自己所擁有的threadLocalMap中,
這樣每個執行緒使用自己儲存的ThreadLocalMap物件,不會影響執行緒之間的隔離。
看到這裡的第一眼我一直以為ThreadLocal是一個map,每一個執行緒都是一個key,對應一個value,但是是不正確的。正確的是每個執行緒持有一個ThreadLocalMap的副本,這個map的鍵是ThreadLocal物件,各個執行緒中同一key對應的值可以不一樣。
ThreadLocal實現原理
ThreadLocal
中的欄位與構造方法
詳細說明參考註釋
public class ThreadLocal<T> { //當前ThreadLocal物件的HashCode值, //通過這個值可以定位Entry物件在ThreadLocalMap中的位置 //由nextHashCode計算得出 private final int threadLocalHashCode = nextHashCode(); //一個自動更新的AtomicInteger值,官方解釋是會自動更新,怎麼更新的不知道, //看完AtomicInteger原始碼回來填坑 private static AtomicInteger nextHashCode = new AtomicInteger(); //ThreadLocal的魔數 //0x61c88647是斐波那契雜湊乘數,它的優點是通過它雜湊(hash)出來的結果分佈會比較均勻,可以很大程度上避免hash衝突, private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { //原子操作:將給定的兩個值相加 return nextHashCode.getAndAdd(HASH_INCREMENT); } /** * 返回當前執行緒變數的初始值 * 這個方法僅在沒有呼叫set方法的時候第一次呼叫get方法呼叫 */ protected T initialValue() { return null; } //構造方法 public ThreadLocal() { } //建立ThreadLocalMap, //當前的ThreadLocal物件和value加入map當中 //賦值給當前執行緒的threadLocals欄位 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ....... }
常用方法get()、set()、remove
get()
方法
get()
方法的原始碼,具體的程式碼解釋請看註釋//返回當前執行緒中儲存的與當前ThreadLocal相關的執行緒變數的值(有點繞,可以看程式碼註釋) public T get() { Thread t = Thread.currentThread(); //返回當前執行緒的threadLocals欄位的值,型別是ThreadLocalMap //暫時可以將ThreadLocalMap當作HashMap,下文解釋 ThreadLocalMap map = getMap(t); if (map != null) { //Entry也可以按照HashMap的entry理解 //Entry儲存了兩個值,一個值是key,一個值是value //返回當前ThreadLocalMap中當前ThreadLcoal對應的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //返回Entry中對應的值 return result; } } //如果當前執行緒的ThreadLocalMap不存在,則構造一個 return setInitialValue(); }
getMap(Thread t)
方法//返回當前執行緒的threadLocals欄位的值,型別為ThreadLocalMap ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
getEntry(ThreadLocal<?> key)
方法//table是一個陣列,具體的可以看下文的ThreadLocalMap解釋 //返回當前ThreadLocalMap中key對應的Entry private Entry getEntry(ThreadLocal<?> key) { //根據key值計算所屬Entry所在的索引位置(同HashMap) 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); }
setInitialValue()
方法private T setInitialValue() { //在構造方法部分有寫,返回一個初始值, //預設情況(沒有被子類重寫)下是一個null值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //再次判斷當前執行緒中threadLocals欄位是否為空值 if (map != null) //set可以看作HashMap中的put map.set(this, value); else //當map為null時 //構造一個ThreadLocalMap, //並以自身為鍵,initialValue()的結果為值插入ThreadLocalMap //並賦值給當前執行緒的threadLocals欄位 createMap(t, value); return value; }
createMap
方法void createMap(Thread t, T firstValue) { //呼叫THreadLocalMap的構造方法 //構造一個ThreadLocalMap, //使用給定的值構造一個儲存的例項(Entry的物件)儲存到map中 //並儲存到thread的threadLocals欄位中 t.threadLocals = new ThreadLocalMap(this, firstValue); }
從
get()
方法中大概可以看出ThreadLocalMap
是一個以ThreadLocal
物件為鍵一個Map,並且這個ThreadLocalMap
物件由Thread
類維護,並儲存在threadLocals
欄位中,不同的ThreadLocal
物件可以以自身為鍵訪問這個Map
中對應位置的值。當第一次呼叫
get()(之前沒有呼叫過set()或者呼叫了remove())
時,會調`initialValue()新增當前ThreadLocal物件對應的值為null並返回。
set()
方法
set()
方法的原始碼,具體的程式碼的解釋請看註釋/** * 設定當前執行緒的執行緒變數的副本為指定值 * 子類一般不用重寫這個方法 * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); //返回當前執行緒的threadLocals欄位的值,型別是ThreadLocalMap,同get()方法 ThreadLocalMap map = getMap(t); //同get()一樣 判斷當前執行緒的threadLocals欄位的是否為null if (map != null) //不為null,設定當前ThreadLocal(key)對應的值(value)為指定的value map.set(this, value); else //null,建立ThreadLocalMap物件,將[t, value]加入map,並賦值給當前執行緒的localThreads欄位 createMap(t, value); }
remove()
方法
remove()
方法的原始碼,具體程式碼的解釋請看註釋public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) //從當前執行緒的threadLocals欄位中移除當前ThreadLocal物件為鍵的鍵值對 //remove方法在 ThreadLocalMap中實現 m.remove(this); }
從上面的實現程式碼看,get()、set()、remove
這三個方法都是很簡單的從一個ThreadLocalMap中獲取設定或者移除值,那麼有一個核心就是ThreadLocalMap
,那麼下面就分析下ThreadLocalMap
類
ThreadLocalMap的實現
個人覺得replaceStaleEntry()
、expungeStaleEntry()
、cleanSomeSlots()
這三個方法是ThreadLocal中非常重要難以理解的方法;
/**
* ThreadLocalMap 是一個定製的雜湊雜湊對映,僅僅用用來維護執行緒本地變數
* 對其的所有操作都在ThreadLocal類裡面。
* 使用軟引用作為這個雜湊表的key值(軟引用引用的物件在強引用解除引用後的下一次GC會被釋放)
* 由於不使用引用佇列,表裡的資料只有在表空間不足時才會被釋放
* (因為使用的時key-value,在key被釋放·null·後這個表對應的位置不會變為null,需要手動釋放)
* 這個map和HashMap不同的地方是,
* 在發生雜湊衝突的時候HashMap會使用連結串列(jdk8之後也可能是紅黑樹)儲存(拉鍊法)
* 而這裡使用的是向後索引為null的表項來儲存(開放地址法)
*/
static class ThreadLocalMap {
/**
* 這個hash Map的條目繼承了 WeakReference, 使用他的ref欄位作為key(一個ThreadLocal物件)
* ThreadLocal object). 注意當鍵值為null時代表整個鍵已經不再被引用(ThreadLocal
* 物件已經被垃圾回收)因此可以刪除對應的條目
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//key值是當前TreadLocal物件的弱引用
super(k);
value = v;
}
}
//類的屬性欄位,基本和HashMap作用一致
//初始容量,必須是2的冪
private static final int INITIAL_CAPACITY = 16;
//雜湊表
//長度必須是2的冪,必要時會調整大小
private Entry[] table;
//表中儲存資料的個數
private int size = 0;
//當表中資料的個數達到這個值時需要擴容
private int threshold; // Default to 0
//設定threshold ,負載係數時2/3,也就是說當前表中資料的條目
//達到表總容量的2/3就需要擴容
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
//這個註釋不明白,但是在程式碼實現中遍歷表的的時候用來判斷是否到了表的結尾
//如果到了表節位就從表首接著這遍歷
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
//這個註釋不明白,但是在程式碼實現中遍歷表的的時候用來判斷是否到了表的頭部
//如果到了表節位就從表尾接著這遍歷
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//構造一個新map包含 (firstKey, firstValue).
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//構造表
table = new Entry[INITIAL_CAPACITY];
//確定需要加入的資料的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
構造一個新的Entry物件並放入到表的對應位置
table[i] = new Entry(firstKey, firstValue);
//設定當前表中的資料個數
size = 1;
// 設定需要擴容的臨界點
setThreshold(INITIAL_CAPACITY);
}
//這個方法在建立一個新執行緒呼叫到Thread.init()方法是會被呼叫
//目的是將父執行緒的inheritableThreadLocals傳遞給子執行緒
//建立的map會被儲存在Thread.inheritableThreadLocals中
//根據parentMap構造一個新的ThreadLocalMap,
//這個map包含了所有parentMap的值
//只有在createInheritedMap呼叫
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
//根據parentMap的長度(容量)構造table
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) {
//childValue在InheritableThreadLocal中實現
//也只有InheritableThreadLocal物件會呼叫這個方法
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++;
}
}
}
}
getEntry()
獲取指定key
對應的Entry
物件
/**
* 通過key獲取key-value對. 這個方法本身只處理快速路徑(直接命中)
* 如果沒有命中繼續前進(getEntryAfterMiss)
* 這是為了使命中效能最大化設計
*/
private Entry getEntry(ThreadLocal<?> key) {
//計算key應該出現在表中的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//判斷獲取到的entry的key是否是給出的key
if (e != null && e.get() == key)
//是就返回entry
return e;
else
//否則向後查詢,
return getEntryAfterMiss(key, i, e);
}
/**
* getEntry的派生,當直接雜湊的槽裡找不到鍵的時候使用
*
* @param key the thread local object
* @param i key在table中雜湊的結果
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//根絕ThreadLocalMapc插入的方法,插入時通過雜湊計算出來的槽位不為null
//則向後索引,找到一個空位放置需要插入的值
//所以從雜湊計算的槽位到插入值的位置中間一定是不為null的
//因為e!=null可以作為迴圈終止條件
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
//如果命中則返回
return e;
if (k == null)
//e!=null && k==null,證明對應的ThreadLcoal物件已經被釋放
//那麼這個位置的entry就可以被釋放
//釋放位置i上的空間
//釋放空間也是ThreadLocalMap與HashMap不相同的地方
expungeStaleEntry(i);
else
//獲取下一個查詢的表的索引下標
//當i>=len時會從0號位重新開始查詢
i = nextIndex(i, len);
e = tab[i];
}
//沒找到返回null
return null;
}
set()
修改或者建立指定的key
對應的Entry
物件
//新增一個key-value對
private void set(ThreadLocal<?> key, Object value) {
// 不像get一樣使用快速路徑,
// set建立新條目和修改現有條目一樣常見
// 這種情況下快速路徑通常會失敗
Entry[] tab = table;
int len = tab.length;
//計算應該插入的槽的位置
int i = key.threadLocalHashCode & (len-1);
//雜湊計算的槽位到插入值的位置中間一定是不為null的
//該位置是否位null可以作為迴圈終止條件
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//修改現有的鍵值對的值
if (k == key) {
e.value = value;
return;
}
//e!=null && k==null,證明對應的ThreadLcoal物件已經被釋放
//那麼這個位置的entry就可以被釋放
//釋放位置i上的空間
//釋放空間也是ThreadLocalMap與HashMap不相同的地方
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//在可以插入值的地方插入
tab[i] = new Entry(key, value);
int sz = ++size;
//清除部分k是null的槽然後判斷是否需要擴容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//擴容
rehash();
}
remove()
移除指定key
對應的Entry
物件
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//同set()
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//弱引用引用置空,標記這個槽已經是舊槽
e.clear();
//清理舊槽
expungeStaleEntry(i);
return;
}
}
}
set()
過程中處理舊槽的核心方法——replaceStaleEntry()
/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
//run:兩個空槽中間所有的非空槽
//姑且翻譯成執行區間
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 備份檢查當前執行中的以前的陳舊條目.
// 我們每次清理整個執行區間,避免垃圾收集器一次釋放過多的引用
// 而導致增量的雜湊
// slotToExpunge刪除的槽的起始位置,因為在後面清除(expungeStaleEntry)
// 的過程中會掃描從當前位置到第一個空槽之間的位置,所以這裡只需要判斷出
// 掃描的開始位置就可以
int slotToExpunge = staleSlot;
//向前掃描找到最前面的舊槽
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
//在向前掃描的過程中找到了舊槽舊覆蓋舊槽的位置
if (e.get() == null)
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
//從傳進來的舊槽的位置往後查詢key值或者第一個空槽結束
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果在staleSlot位置的槽後面找到了指定的key,那麼將他和staleSlot位置的槽進行交換
// 以保持雜湊表的順序
// 然後將新的舊槽護著上面遇到的任何過期的槽通過expungeStaleEntry刪除
// 或者重新雜湊所有執行區間的其他條目
//找到了對應的key,將他和staleSlot位置的槽進行交換
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 判斷清理舊槽的開始位置
// 如果在將staleSlot之前沒有舊槽,那麼就從當前位置為起點清理
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//expungeStaleEntry清理從給定位置開始到第一個null的區間的空槽
//並返回第一個null槽的位置p
//cleanSomeSlots從expungeStaleEntry返回的位置p開始清理log(len)次表
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
//將給定的key-value已經存放,也清理了相應執行區間 ,返回
return;
}
// 如果在向後查詢後沒有找到對應的key值,而當前的槽是舊槽
// 同時如果在向前查詢中也沒查詢到舊槽
// 那麼進行槽清理的開始位置就是當前位置
//為什麼不是staleSlot呢?因為在這個位置建立指定的key-value存放
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 如果在向後查詢後沒有找到對應的key值,在staleSlot位置建立該key-value
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 確定掃描過程中發現過空槽
if (slotToExpunge != staleSlot)
//清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
replaceStaleEntry()
計算了很久的掃描清理的起點位置,總結下應該分為四種情況:
prev
方向上沒有舊槽,在next
方向上找到key
值之前沒有找到舊槽,那麼就交換key
和staleSlot
然後從當前位置向後清理空槽prev
方向上沒有舊槽,在next
方向上沒有找到key
沒有找到舊槽,那麼在staleSlot
位置建立指定的key-value
,prev
方向上沒有舊槽,在next
方向上沒有找到key
但是找到舊槽,那麼在staleSlot
位置建立指定的key-value
,並從找到的第一個舊槽的位置開始清理舊槽prev
方向上找到舊槽,在next
方向上沒有找到key
,那麼在staleSlot
位置建立指定的key-value
,從prev
方向最後一個找到的舊槽開始清理舊槽prev
方向上找到舊槽,在next
方向上找到key
,那麼就交換key
和staleSlot
,從prev
方向最後一個找到的舊槽開始清理舊槽
求推薦個好看的畫圖工具
清理舊槽的核心方法——expungeStaleEntry()
和cleanSomeSlots()
/**
* 刪除指定位置上的舊條目,並掃描從當前位置開始到第一個發現的空位之間的陳舊條目
* 還會刪除尾隨的第一個null之前的所有舊條目
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 釋放槽
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 重新雜湊直到遇到null
Entry e;
int i;
//第staleSlot槽已經空出來,
//從下一個槽開始掃描舊條目直到遇到空槽
//因為整個表只適用2/3的空間,所以必然會遇到空槽
//刪除掃描期間遇到的空槽
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//遇到的空槽直接刪除
if (k == null) {
e.value = null;
tab[i] = null;
size--;
//非空槽通過重新雜湊找到清理後適當的儲存位置
} else {
int h = k.threadLocalHashCode & (len - 1);
//重新雜湊的結果不在原位置,那麼將原位置的槽空出來
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
//尋找到從第h位開始的第一個空槽放置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//返回掃描過程中遇到的第一個空槽
return i;
}
/**
* Heuristically scan some cells looking for stale entries.
* This is invoked when either a new element is added, or
* another stale one has been expunged. It performs a
* logarithmic number of scans, as a balance between no
* scanning (fast but retains garbage) and a number of scans
* proportional to number of elements, that would find all
* garbage but would cause some insertions to take O(n) time.
*
* @param i a position known NOT to hold a stale entry. The
* scan starts at the element after i.
*
* @param n scan control: {@code log2(n)} cells are scanned,
* unless a stale entry is found, in which case
* {@code log2(table.length)-1} additional cells are scanned.
* When called from insertions, this parameter is the number
* of elements, but when from replaceStaleEntry, it is the
* table length. (Note: all this could be changed to be either
* more or less aggressive by weighting n instead of just
* using straight log n. But this version is simple, fast, and
* seems to work well.)
*
* @return true if any stale entries have been removed.
*/
//掃描一部分表清理其中的就條目,
//掃描log(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];
//key=e.get(),e不為null,但key為null
if (e != null && e.get() == null) {
n = len;
//標記清理過某個槽
removed = true;
//清理過程
i = expungeStaleEntry(i);
}
//只掃描log(n)個槽,一方面可以保證避免過多的空槽的堆積
//一方面可以保證插入或者刪除的效率
//因為刪除的時候會掃描兩個空槽之前的槽位
//每個槽位全部掃描的話時間複雜度會高,
//因為在expungeStaleEntry掃描當前位置到第一個空槽之間所有的舊槽
//所以在這裡進行每個槽位的掃描會做很多重複的事情,效率低下
//雖然掃描從i開始的log(n)也會有很多重複掃描,但是通過優良的雜湊演算法
//可以減少雜湊衝突也就可以減少重複掃描的數量
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
//擴容至當前表的兩倍容量
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
//在新的雜湊表中的雜湊位置
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//設定下一個需要擴容的臨界量
setThreshold(newLen);
size = count;
//替換舊錶
table = newTab;
}
/**
* Expunge all stale entries in the table.
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
一些問題
- 為什麼使用弱引用:
因為使用弱引用不會影響ThreadLocal物件被釋放後的垃圾回收,由於使用了弱引用,
被釋放的物件只能存活到下一次gc
,物件被回收後弱引用就變為null,這時候就可以進行判斷這個位置的條目是否已經是舊條目,
從而進行清理。防止記憶體洩漏,
儘管軟引用的物件也可以被垃圾回收掉,但是物件會存活到記憶體不足這樣會造成記憶體洩漏 - 如何解決舊的槽中
Entry
物件不被回收造成記憶體洩漏問題:在get()
中碰到舊槽會通過expungeStaleEntry()
方法來清理舊槽,
清理完成後會繼續向後清理直到遇到第一個空槽,在此期間會進行雜湊重定位操作,
將每個槽中的都西昂放在合適的位置以維持雜湊表的順序;
在set()
方法中呼叫replaceStaleEntry
-->expungeStaleEntry()
清理整個執行區間內的舊槽,
並呼叫cleanSomeSlots()
迴圈掃描log(n)個槽位進行清理,使用優秀的雜湊演算法減少每次呼叫到expungeStaleEntry()
重複清理同一區間的工作;
\(\color{#FF3030}{轉載請標明出處}\)