日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ThreadLocal

發布時間:2024/8/1 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ThreadLocal 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • ThreadLocal
    • 1、基本認識
    • 2、內部結構
    • 3、ThreadLocal常用方法
    • 4、ThreadLocalMap的源碼解析
    • 5、內存泄漏

ThreadLocal

1、基本認識

作用:提供線程內的局部變量,不同的線程之間互不干擾,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或組件之間的一些公共變量傳遞的復雜度。

淺拿生活中的例子理解一波:

  • 每個房子就是每個線程,每個房子中的清潔工具為對應的房子獨有,清潔工具就是ThreadLocalMap
  • 每個房子的清潔工具的使用互不干擾
  • 清潔工具含有掃把,拖把等,掃把和拖把是不同的Threadlocal對象,ThreadLocalMap存儲的記錄entry中的鍵key是Threadlocal對象引用,各個房子的清潔工具可以適用在房子的各個樓層,各個樓層即不同組件。

總結:多線程并發情況下,線程隔離,每個線程的變量都是獨立的,不會互相影響,通過Threadlocal對象在同一線程不同組件間傳遞公共變量。

2、內部結構

圖源B站視頻

好處:

  • 當Thread銷毀的時候,ThreadLocalMap也會隨之銷毀,減少內存的使用

解讀 JDK 8 結構:

  • Thread維護一個ThreadLocalMap,ThreadLocalMap內部是一個Entry數組,一個Entry包含key 和 value,key 為不同的ThreadLocal對象(注:泛型ThreadLocal<?>)

3、ThreadLocal常用方法

public T get() {Thread t = Thread.currentThread();//獲取當前線程ThreadLocalMap map = getMap(t);//獲取當前線程所維護的ThreadLocalMap//注:每個線程維護自己的ThreadLocalMap 相當于我前文例子的清潔工具if (map != null) {//獲取當前ThreadLocal的記錄ThreadLocalMap.Entry e = map.getEntry(this);//this,即調用這個方法的ThreadLocalif (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;//得到ThreadLocal的value值return result;}}/* 有兩種情況會執行到這里1、當前線程的ThreadLocalMap還未創建2、沒有該ThreadLocal的entry記錄*/return setInitialValue(); } private T setInitialValue() {T value = initialValue();//默認返回null,子類可以重寫該方法Thread t = Thread.currentThread();//獲取當前線程ThreadLocalMap map = getMap(t);//獲取當前線程所維護的ThreadLocalMapif (map != null)//沒有該ThreadLocal的entry記錄map.set(this, value);//增加該記錄elsecreateMap(t, value);//當前線程的ThreadLocalMap還未創建return value; }public void set(T value) {Thread t = Thread.currentThread();//獲取當前線程ThreadLocalMap map = getMap(t);//獲取當前線程所維護的ThreadLocalMapif (map != null)map.set(this, value);//增加該記錄elsecreateMap(t, value);//當前線程的ThreadLocalMap還未創建 } //移除map中的某個entry public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());//獲取當前線程的ThreadLocalMapif (m != null)m.remove(this);//刪除 key 值為 調用該方法的ThreadLocal 的記錄 }

總結:

  • 第一次調用set() 或get() 方法時都可以創建ThreadLocalMap,初始值不同,get()的初始值默認為null(子類可重寫獲取初始值的方法initialValue())

4、ThreadLocalMap的源碼解析

ThreadLocalMap是ThreadLocal中的內部類,是獨立實現的類似于HashMap的類,并沒有實現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數組的初始化長度,與hashmap一樣 必須是2的冪次方*/private static final int INITIAL_CAPACITY = 16;/*** entry數組*/private Entry[] table;/*** 數組存儲的記錄個數*/private int size = 0;/*** 擴容的閾值,超過該值則進行擴容,此時默認為0*/private int threshold; /*** 擴容的閾值的設置*/private void setThreshold(int len) {threshold = len * 2 / 3;}/*** 像一個環一樣,從索引下標 i 走到末尾,到達末尾之后下一步走 0 下標*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** 像一個環一樣,從索引下標 i 走到0,到達0之后下一步走 len - 1 下標*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/** * 創建一個map,當有記錄需插入時,數組才初始化*/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 時調用* 創建一個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 或者 當前索引對應的記錄 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) {//因為 當前索引對應的記錄 key值 不等于 key 調用的該方法ThreadLocal<?> k = e.get();if (k == key)//找到該記錄return e;if (k == null) expungeStaleEntry(i);//置該記錄為null 致使下次垃圾回收時回收該空間elsei = nextIndex(i, len);//遍歷數組e = tab[i];}return null;// 1、因為 e == null 調用該方法 即說明該key值對應的記錄 不存在// 2、遍歷完數組 key值對應的記錄不存在}private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;//ThreadLocalMap內的entry數組int len = tab.length;int i = key.threadLocalHashCode & (len-1);//通過與運算得到下標索引//可以把數組理解為一個環,向右檢索是否有空位置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) {//檢索過程,發現有key值為null的(對應的ThreadLocal已被垃圾回收),替換記錄replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);//e == null 退出循環或者當前下標的數組位置不為空int sz = ++size;//記錄數 + 1//cleanSomeSlots(i, sz) 這個過程清除數組中key值為null的記錄if (!cleanSomeSlots(i, sz) && sz >= threshold)//數組中沒有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下標后的所有記錄重新hashEntry e;int i;//這個過程就是在查看是否還有null值//以及重新定位一些因為哈希沖突而不在k.threadLocalHashCode & (len - 1)下標的記錄//因為在獲取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) //數組初始長度為16時, size >= 8時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; // 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、內存泄漏

內存泄漏的概念:指程序中已動態分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果,內存泄漏的堆積終將導致內存溢出。

  • 如果key使用強引用
    假設業務代碼中使用完ThreadLocal,那么ThreadLocal的引用對象被回收了
  • 但是ThreadLocalMap強引用了ThreadLocal,造成ThreadLocal無法被回收
  • 在沒有手動刪除這個entry的情況下,始終有當前線程指向ThreadLocalMap,>entry就不會被回收,entry包含ThreadLocal實例和value,導致entry內存泄漏。
    也就是說key使用了強引用是無法完全避免內存泄露
  • 如果key使用弱引用
    同樣假設業務代碼中使用完ThreadLocal,那么ThreadLocal的引用被回收了
  • 由于ThreadLocalMap只持有ThreadLocal的弱引用,沒有任何強引用指向ThreadLocal實例,所以ThreadLocal被垃圾回收,此時entry中的key = null
  • 在沒有手動刪除這個entry的情況下,始終有當前線程指向ThreadLocalMap,由于key = null,那么對應的value就永遠都不會被訪問到,導致value內存泄漏。

比較以上兩種情況,內存泄漏的發生跟ThreadLocalMap中的 key是否使用弱引用是沒有關系的
在以上兩種內存泄漏的情況中,都有兩個前提:

  • 沒有手動刪除這個Entry
  • CurrentThread依然運行
  • 第一點:只要在使用完ThreadLocal,調用其remove方法刪除對應的Entry,就能避免內存泄漏。
    第二點:由于ThreadLocalMap是Thread的一個屬性,被當前線程所引用,所以它的生命周期跟Thread一樣長。那么在使用完ThreadLocal的使用,如果當前Thread也隨之執行結束,ThreadLocalMap自然也會被gc回收,從根源上避免了內存泄漏。

    在JDK 8 中是由線程來維護ThreadLocalMap,因此ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動remove方法就會導致內存泄漏。

    總結: 實際上內存泄漏與key是強引用還是弱引用無關,弱引用反而比強引用多一層保障。ThreadLocalMap內部會通過key 是否等于null來檢驗,若是則置value等于null,防止用戶未調用remove方法移除entry。

    總結

    以上是生活随笔為你收集整理的ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。