并發編程之 ThreadLocal( 三 )


public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)// this 指的是 ThreadLocal 對象 , value 就是想要設置進去的值map.set(this, value);elsecreateMap(t, value);}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}map.set(this, value); 需要注意的是,這個 map 以及 map 中的 key 和 value 都是保存在 Thread 線程中的,而不是保存在 ThreadLocal 中 。
remove原理跟 get 和 set 類似,這里就不贅述了 。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}ThreadLocal 的內存泄露內存泄漏:當某個對象不再有引用 , 但是所占用的內存不能被回收 。
下面我們來看 ThreadLocal 的靜態內部類 ThreadLocalMap ,ThreadLocalMap 的 Entry 其實就是存放每一個ThreadLocal 和 value 鍵值對的集合 。

并發編程之 ThreadLocal

文章插圖

并發編程之 ThreadLocal

文章插圖
Entry 靜態類的構造方法,分別執行了 super(k); value = https://www.huyubaike.com/biancheng/v; 其中 super(k) 去父類中進行初始化,而從 Entry extends 的父類我們可以看出,WeakReference 父類是一個弱引用類,則說明了 k 值是一個弱引用的, 而 value 就是一個強引用 。
強引用:任何時候都不會被回收,即使發生 GC 的時候也不會被回收(賦值就是一種強引用)
弱引用:對象只被弱引用關聯,在下一次 GC 時會被回收 。(可以理解為只要觸發一次GC,就可以掃描到并被回收掉)
由此我們可以得知,ThreadLocalMap 的每一個 Entry 都是一個對 key 的弱引用,但是每一個 Entry 都包含了一個對 value 的強引用 。而由于線程池中的線程池存活時間都比較長,那么 Entry 的 key 是可以被回收掉的 , 但是 value 無法被回收,就會發生內存泄漏 。
JDK 的設計者也考慮到了這個不足之處,所以在經常調用的方法,比如 set, remove, rehash 會主動去掃描 key 為 null 的 Entry,并把對應的 value 設置 null , 這樣 value 對象也可以被 GC 給回收掉 。
另外在阿里巴巴 Java 開發手冊也明確指出,應該顯式地調用 remove 方法,刪除 Entry 對象 , 避免內存泄漏 。
【強制】 必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被復用,如果不清理自定義的 ThreadLocal 變量,可能會影響到后續業務邏輯和造成內存泄漏等問題 。盡量在代碼中使用 try-finally 塊進行回收 。
objThreadLocal.set(someObject);try{ ...} finally { objThreadLocal.remove();}

推薦閱讀