ThreadLocal
文章目錄
- ThreadLocal
- 1、基本認(rèn)識
- 2、內(nèi)部結(jié)構(gòu)
- 3、ThreadLocal常用方法
- 4、ThreadLocalMap的源碼解析
- 5、內(nèi)存泄漏
ThreadLocal
1、基本認(rèn)識
作用:提供線程內(nèi)的局部變量,不同的線程之間互不干擾,這種變量在線程的生命周期內(nèi)起作用,減少同一個線程內(nèi)多個函數(shù)或組件之間的一些公共變量傳遞的復(fù)雜度。
淺拿生活中的例子理解一波:
- 每個房子就是每個線程,每個房子中的清潔工具為對應(yīng)的房子獨有,清潔工具就是ThreadLocalMap
- 每個房子的清潔工具的使用互不干擾
- 清潔工具含有掃把,拖把等,掃把和拖把是不同的Threadlocal對象,ThreadLocalMap存儲的記錄entry中的鍵key是Threadlocal對象引用,各個房子的清潔工具可以適用在房子的各個樓層,各個樓層即不同組件。
總結(jié):多線程并發(fā)情況下,線程隔離,每個線程的變量都是獨立的,不會互相影響,通過Threadlocal對象在同一線程不同組件間傳遞公共變量。
2、內(nèi)部結(jié)構(gòu)
圖源B站視頻
好處:
- 當(dāng)Thread銷毀的時候,ThreadLocalMap也會隨之銷毀,減少內(nèi)存的使用
解讀 JDK 8 結(jié)構(gòu):
- Thread維護一個ThreadLocalMap,ThreadLocalMap內(nèi)部是一個Entry數(shù)組,一個Entry包含key 和 value,key 為不同的ThreadLocal對象(注:泛型ThreadLocal<?>)
3、ThreadLocal常用方法
public T get() {Thread t = Thread.currentThread();//獲取當(dāng)前線程ThreadLocalMap map = getMap(t);//獲取當(dāng)前線程所維護的ThreadLocalMap//注:每個線程維護自己的ThreadLocalMap 相當(dāng)于我前文例子的清潔工具if (map != null) {//獲取當(dāng)前ThreadLocal的記錄ThreadLocalMap.Entry e = map.getEntry(this);//this,即調(diào)用這個方法的ThreadLocalif (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;//得到ThreadLocal的value值return result;}}/* 有兩種情況會執(zhí)行到這里1、當(dāng)前線程的ThreadLocalMap還未創(chuàng)建2、沒有該ThreadLocal的entry記錄*/return setInitialValue(); } private T setInitialValue() {T value = initialValue();//默認(rèn)返回null,子類可以重寫該方法Thread t = Thread.currentThread();//獲取當(dāng)前線程ThreadLocalMap map = getMap(t);//獲取當(dāng)前線程所維護的ThreadLocalMapif (map != null)//沒有該ThreadLocal的entry記錄map.set(this, value);//增加該記錄elsecreateMap(t, value);//當(dāng)前線程的ThreadLocalMap還未創(chuàng)建return value; }public void set(T value) {Thread t = Thread.currentThread();//獲取當(dāng)前線程ThreadLocalMap map = getMap(t);//獲取當(dāng)前線程所維護的ThreadLocalMapif (map != null)map.set(this, value);//增加該記錄elsecreateMap(t, value);//當(dāng)前線程的ThreadLocalMap還未創(chuàng)建 } //移除map中的某個entry public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());//獲取當(dāng)前線程的ThreadLocalMapif (m != null)m.remove(this);//刪除 key 值為 調(diào)用該方法的ThreadLocal 的記錄 }總結(jié):
- 第一次調(diào)用set() 或get() 方法時都可以創(chuàng)建ThreadLocalMap,初始值不同,get()的初始值默認(rèn)為null(子類可重寫獲取初始值的方法initialValue())
4、ThreadLocalMap的源碼解析
ThreadLocalMap是ThreadLocal中的內(nèi)部類,是獨立實現(xiàn)的類似于HashMap的類,并沒有實現(xiàn)Map接口
static class ThreadLocalMap {/*** entry 繼承弱引用, 即key 值 存儲的是ThreadLocal弱引用對象*/static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** entry數(shù)組的初始化長度,與hashmap一樣 必須是2的冪次方*/private static final int INITIAL_CAPACITY = 16;/*** entry數(shù)組*/private Entry[] table;/*** 數(shù)組存儲的記錄個數(shù)*/private int size = 0;/*** 擴容的閾值,超過該值則進(jìn)行擴容,此時默認(rèn)為0*/private int threshold; /*** 擴容的閾值的設(shè)置*/private void setThreshold(int len) {threshold = len * 2 / 3;}/*** 像一個環(huán)一樣,從索引下標(biāo) i 走到末尾,到達(dá)末尾之后下一步走 0 下標(biāo)*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** 像一個環(huán)一樣,從索引下標(biāo) i 走到0,到達(dá)0之后下一步走 len - 1 下標(biāo)*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/** * 創(chuàng)建一個map,當(dāng)有記錄需插入時,數(shù)組才初始化*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}/*** 該方法是在 createInheritedMap 時調(diào)用* 創(chuàng)建一個map 包含所有從父線程繼承的ThreadLocal記錄* parentMap 父線程的ThreadLocalMap*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];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++;}}}}/*** 獲取entry*/private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;else //e == null 或者 當(dāng)前索引對應(yīng)的記錄 key值 不等于 keyreturn getEntryAfterMiss(key, i, e);}private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {//因為 當(dāng)前索引對應(yīng)的記錄 key值 不等于 key 調(diào)用的該方法ThreadLocal<?> k = e.get();if (k == key)//找到該記錄return e;if (k == null) expungeStaleEntry(i);//置該記錄為null 致使下次垃圾回收時回收該空間elsei = nextIndex(i, len);//遍歷數(shù)組e = tab[i];}return null;// 1、因為 e == null 調(diào)用該方法 即說明該key值對應(yīng)的記錄 不存在// 2、遍歷完數(shù)組 key值對應(yīng)的記錄不存在}private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;//ThreadLocalMap內(nèi)的entry數(shù)組int len = tab.length;int i = key.threadLocalHashCode & (len-1);//通過與運算得到下標(biāo)索引//可以把數(shù)組理解為一個環(huán),向右檢索是否有空位置for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//得到entry的key值ThreadLocalif (k == key) {//相等,說明ThreadLocal的記錄已存在e.value = value;//更新return;}if (k == null) {//檢索過程,發(fā)現(xiàn)有key值為null的(對應(yīng)的ThreadLocal已被垃圾回收),替換記錄replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);//e == null 退出循環(huán)或者當(dāng)前下標(biāo)的數(shù)組位置不為空int sz = ++size;//記錄數(shù) + 1//cleanSomeSlots(i, sz) 這個過程清除數(shù)組中key值為null的記錄if (!cleanSomeSlots(i, sz) && sz >= threshold)//數(shù)組中沒有key值為null的記錄且超過擴容閾值rehash();//擴容}} /*** 刪除entry記錄*/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;}}}private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;tab[staleSlot].value = null;tab[staleSlot] = null;size--;// 將i下標(biāo)后的所有記錄重新hashEntry e;int i;//這個過程就是在查看是否還有null值//以及重新定位一些因為哈希沖突而不在k.threadLocalHashCode & (len - 1)下標(biāo)的記錄//因為在獲取entry時,就是通過key值得到得索引一直向右遍歷找到,如果不重新定位,可能就造成,實際上該記錄存在,但最終沒查找到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) {//說明 原本key得到的索引 造成哈希沖突tab[i] = null;while (tab[h] != null)//重新定位該key值h = nextIndex(h, len);tab[h] = e;}}}return i;}private boolean cleanSomeSlots(int i, int n) {//是否存有key為null的記錄boolean removed = false;Entry[] tab = table;int len = tab.length;do {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;}private void rehash() {expungeStaleEntries();if (size >= threshold - threshold / 4) //數(shù)組初始長度為16時, size >= 8時resize();//擴容}/*** 擴容為原來數(shù)組的兩倍*/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; // 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;}/*** 刪除所有 key 為 null 的記錄*/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);}} }5、內(nèi)存泄漏
內(nèi)存泄漏的概念:指程序中已動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果,內(nèi)存泄漏的堆積終將導(dǎo)致內(nèi)存溢出。
- 如果key使用強引用
假設(shè)業(yè)務(wù)代碼中使用完ThreadLocal,那么ThreadLocal的引用對象被回收了 - 但是ThreadLocalMap強引用了ThreadLocal,造成ThreadLocal無法被回收
- 在沒有手動刪除這個entry的情況下,始終有當(dāng)前線程指向ThreadLocalMap,>entry就不會被回收,entry包含ThreadLocal實例和value,導(dǎo)致entry內(nèi)存泄漏。
也就是說key使用了強引用是無法完全避免內(nèi)存泄露
- 如果key使用弱引用
同樣假設(shè)業(yè)務(wù)代碼中使用完ThreadLocal,那么ThreadLocal的引用被回收了 - 由于ThreadLocalMap只持有ThreadLocal的弱引用,沒有任何強引用指向ThreadLocal實例,所以ThreadLocal被垃圾回收,此時entry中的key = null
- 在沒有手動刪除這個entry的情況下,始終有當(dāng)前線程指向ThreadLocalMap,由于key = null,那么對應(yīng)的value就永遠(yuǎn)都不會被訪問到,導(dǎo)致value內(nèi)存泄漏。
比較以上兩種情況,內(nèi)存泄漏的發(fā)生跟ThreadLocalMap中的 key是否使用弱引用是沒有關(guān)系的
在以上兩種內(nèi)存泄漏的情況中,都有兩個前提:
第一點:只要在使用完ThreadLocal,調(diào)用其remove方法刪除對應(yīng)的Entry,就能避免內(nèi)存泄漏。
第二點:由于ThreadLocalMap是Thread的一個屬性,被當(dāng)前線程所引用,所以它的生命周期跟Thread一樣長。那么在使用完ThreadLocal的使用,如果當(dāng)前Thread也隨之執(zhí)行結(jié)束,ThreadLocalMap自然也會被gc回收,從根源上避免了內(nèi)存泄漏。
在JDK 8 中是由線程來維護ThreadLocalMap,因此ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動remove方法就會導(dǎo)致內(nèi)存泄漏。
總結(jié): 實際上內(nèi)存泄漏與key是強引用還是弱引用無關(guān),弱引用反而比強引用多一層保障。ThreadLocalMap內(nèi)部會通過key 是否等于null來檢驗,若是則置value等于null,防止用戶未調(diào)用remove方法移除entry。
總結(jié)
以上是生活随笔為你收集整理的ThreadLocal的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何编写优秀的单元测试用例
- 下一篇: android qq轻聊版多个,Andr