這篇文章帶給大家的內容是關於ThreadLocal的實現原理的分析介紹(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
ThreadLocal,即執行緒局部變量,用來為每一個使用它的執行緒維護一個獨立的變數副本。這種變數只在執行緒的生命週期內有效。且與鎖定機制那種以時間換取空間的做法不同,ThreadLocal沒有任何鎖定機制,它以空間換取時間的方式保證變數的執行緒安全性。
本篇從原始碼方面分析ThreadLocal的實作原理。
先看ThreadLocal類別圖結構
#SuppliedThreadLocal主要是JDK1.8用來擴展對Lambda表達式的支持,有興趣的自行百度。 ThreadLocalMap是ThreadLocal的靜態內部類別,也是實際保存變數的類別。
下面以一張圖簡單說明Thread,ThreadLocal,ThreadLocalMap和Entry的關係。
#說明上圖:
一個Thread擁有一個ThreadLocalMap物件
#ThreadLocalMap擁有一個Entry陣列
每個Entry都有k--v
Entry的key就是某個具體的ThreadLocal物件
#以下分析主要方法。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
這裡可以看出:一個Thread只擁有一個ThreadLocalMap物件;具體的存值呼叫是ThreadLocalMap的set(),傳入的參數key就是目前ThreadLocal物件。
再看看ThreadLocalMap的set()方法:
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 1 for (Entry e = tab[i]; // 2 e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); // 3 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4 rehash(); }
#從目前index開始遍歷,清除key為null的無效Entry
將K-V封裝為Entry,放入陣列
判斷是否需要進行Entry數組擴容。 threshold的值為陣列容量的2/3。
看看擴容的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; }
#ThreadLocal的get()方法如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); // 1 Entry e = table[i]; if (e != null && e.get() == key) // 2 return e; else return getEntryAfterMiss(key, i, e); //3 } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { //4 ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
目前index上的Entry不為空且key相同,直接回傳
否則去相鄰index尋找
#循環找,發現無效key就清除。找到就結束循環。
3、remove()#########public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }######## #處理方式和尋找儲存類似,刪除對應Entry後都會移除key為null的無效元素。 ############注意###############
static class Entry extends WeakReference<ThreadLocal<?>> {}#########ThreadLocal可能有OOM問題。因為ThreadLocalMap是使用ThreadLocal的弱引用作為key的,發生GC時,key被回收,這樣我們就無法存取key為null的value元素,如果value本身是較大的對象,那麼線程一直不結束的話,value就一直無法得到回收。特別是在我們使用線程池時,線程是複用的,不會殺死線程,這樣ThreadLocal弱引用被回收時,value不會被回收。 ######在使用ThreadLocal時,執行緒邏輯程式碼結束時,必須顯示呼叫ThreadLocal.remove()方法。 ###
以上是ThreadLocal的實作原理的分析介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!