弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出
ThreadLocal徹底詳解
整理本文主要是想幫助大家完全消化面試中常見的ThreadLocal問題。希望讀懂此文以后大家可以掌握:
1. 簡介
高并發處理起來比較麻煩,很多新手對此都會非常頭疼。要知道避免并發的最簡單辦法就是線程封閉,也即是把對象封裝到一個線程里,那么對象就只會被當前線程能看到,使得對象就算不是線程安全的也不會出現任何安全問題。Threadlocal是實現該策略的最好的方法。Threadlocal為每個線程提供了一個私有變量,然后線程訪問該變量(get或者set)的時候實際上是讀寫的自己的局部變量從而避免了并發法問題。
2. 案例使用
首先定義一個ThreadLocal的封裝工具類
package bigdata.spark.study.ThreadLocalTest;public class Bank {ThreadLocal<Integer> t = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 100;}};public int get(){return t.get();}public void set(){t.set(t.get()+10);} }實現一個Runnable對象然后使用bank對象
package bigdata.spark.study.ThreadLocalTest;public class Transfer implements Runnable {Bank bank;public Transfer(Bank bank) {this.bank = bank;}@Overridepublic void run() {for (int i =0 ;i < 10;i++){bank.set();System.out.println(Thread.currentThread()+" : " +bank.get());}} }定義兩個線程t1和t2,運行之后查看結果:
package bigdata.spark.study.ThreadLocalTest;public class Test {public static void main(String[] args) {Bank bank = new Bank();Transfer t = new Transfer(bank);Thread t1 = new Thread(t);t1.start();Thread t2 = new Thread(t);t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(bank.get());} }查看輸出結果就會發現,發現主線程,線程t1,線程t2之間相互不影響~
Thread[Thread-0,5,main] : 110 Thread[Thread-0,5,main] : 120 Thread[Thread-0,5,main] : 130 Thread[Thread-0,5,main] : 140 Thread[Thread-0,5,main] : 150 Thread[Thread-0,5,main] : 160 Thread[Thread-0,5,main] : 170 Thread[Thread-0,5,main] : 180 Thread[Thread-0,5,main] : 190 Thread[Thread-0,5,main] : 200 Thread[Thread-1,5,main] : 110 Thread[Thread-1,5,main] : 120 Thread[Thread-1,5,main] : 130 Thread[Thread-1,5,main] : 140 Thread[Thread-1,5,main] : 150 Thread[Thread-1,5,main] : 160 Thread[Thread-1,5,main] : 170 Thread[Thread-1,5,main] : 180 Thread[Thread-1,5,main] : 190 Thread[Thread-1,5,main] : 200 1003. 底層源碼
每個線程Thread內部都會有ThreadLocal.ThreadLocalMap對象,該對象是一個自定義的map,key是弱引用包裝的ThreadLocal類型,value就是我們的值。
初始值
Threadlocal直接在構造的時候設置初始值。主要是要實現其initialValue方法:
new ThreadLocal<Integer>(){@Overrideprotected IntegerinitialValue() {return 100;} };追蹤一下該方法,會發現其僅僅被一個私有方法調用了
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value; }解讀一下setInitialValue私有方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
讀到這可能會很好奇,為啥只是被私有方法調用,我們又無權調用該私有方法,如何實現初始化呢?也是很簡單的在我們第一次調用get的時候,會調用該私有初始化方法,來真正完成初始化。
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(); }我們來解讀一下get方法,此處就真正暴露ThreadLocal的真實面目了。
Set
接下來解讀一下threadlocal變量的set方法。Set的方法源碼如下:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }1. 獲取當前線程對象 t
2. 通過getMap(t)方法來獲取t內部的ThreadLocal.ThreadLocalMap對象 map。
3. map不為空,當前threadlocal對象作為key(弱引用),要設置的value作為value完成值的設置。
4. 假如map為空,就調用createMap方法,給當前線程創建一個ThreadlocalMap
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); }remove方法
threadlocal的remove方法主要作用是刪除當前threadlocal對應的鍵值對。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this); }4. 內存泄漏
根據前面對threadlocal的整理,其實可以畫出來一個結構圖:
對value的引用線路有兩條:
由于ThreadlocalMap存活時間和線程一樣,比如我們采用的是常駐線程池,使用線程過程中沒有清空ThreadLocalMap,也沒有調用threadlocal的remove方法,就將線程放回線程池,雖然ThreadLocal的強引用ThreadLocalRef被清除,弱引用key在GC的時候也會被設置為null,但是對于value值還存在一條強引用鏈條:
currentThreadRef-àcurrentThread-àThreadLocalMap-àEntry(value),所以value并沒有釋放,就造成了內存泄漏了。
那這時候你或許會問為啥ThreadLocalMap存儲value的時候不采用弱引用呢?這樣不就可以避免內存泄漏了么?value是弱引用是不行的,原因很簡單:我們存儲的對象除了ThreadLocalMap的Value就沒有其他的引用了,value一但是對象的弱引用,GC的時候被回收,對象就無法訪問了,這顯然不是我們想要的。
5. 避免內存泄漏
為避免內存泄漏最好在使用完ThreadLocal之后調用其remove方法,將數據清除掉。
當然,對于Java8 ThreadLocalMap 的 set 方法通過調用 replaceStaleEntry 方法回收鍵為 null 的 Entry 對象的值(即為具體實例)以及 Entry 對象本身從而防止內存泄漏
get方法會間接調用expungeStaleEntry 方法將鍵和值為 null 的 Entry 設置為 null 從而使得該 Entry 可被回收
總結
以上是生活随笔為你收集整理的弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: http 和 https_Golang设
- 下一篇: delphi语言转为汇编语言_每天5分钟