- JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
- JVM利用調用remove、get、set方法的時候,回收弱引用。
- 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那么將導致內存泄漏。
- 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那么,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。http://www.importnew.com/22039.html
那么如何有效的避免呢?
事實上,在ThreadLocalMap中的set/getEntry方法中,會對key為null(也即是ThreadLocal為null)進行判斷,如果為null的話,那么是會對value置為null的。我們也可以通過調用ThreadLocal的remove方法進行釋放!
threadlocal里面使用了一個存在弱引用的map,當釋放掉threadlocal的強引用以后,map里面的value卻沒有被回收.而這塊value永遠不會被訪問到了. 所以存在着內存泄露. 最好的做法是將調用threadlocal的remove方法.
在threadlocal的生命周期中,都存在這些引用. 看下圖: 實線代表強引用,虛線代表弱引用.
每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.
所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什么好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。
PS.Java為了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value。所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然后使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那么這個期間就會發生真正的內存泄露。
應用場景
最常見的ThreadLocal使用場景為 用來解決 數據庫連接、Session管理等。如
private static ThreadLocal < Connection > connectionHolder = new ThreadLocal < Connection > () { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); } private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
一、目錄
二、ThreadLocal是什么?有什么用?
/** * 回顧synchronized在多線程共享線程的問題 * @author qiuyongAaron */ public class ThreadLocalOne { volatile Person person=new Person(); public synchronized String setAndGet(String name){ //System.out.print(Thread.currentThread().getName()+":"); person.name=name; //模擬網絡延遲 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return person.name; } public static void main(String[] args) { ThreadLocalOne threadLocal=new ThreadLocalOne(); new Thread(()->System.out.println(threadLocal.setAndGet("arron")),"t1").start(); new Thread(()->System.out.println(threadLocal.setAndGet("tony")),"t2").start(); } } class Person{ String name="tom"; public Person(String name) { this.name=name; } public Person(){} } 運行結果: 無synchronized: t1:tony t2:tony 有synchronized: t1:arron t2:tony
- 無synchronized的時候,因為非原子操作,顯然不是預想結果,可參考我關於synchronized的討論。
- 現在,我們的需求是:每個線程獨立的設置獲取person信息,不被線程打擾。
- 因為,person是共享數據,用同步互斥鎖synchronized,當一個線程訪問共享數據的時候,其他線程堵塞,不再多余贅述。
/** * 談談ThreadLocal的作用 * @author qiuyongAaron */ public class ThreadLocalThree { ThreadLocal<Person> threadLocal=new ThreadLocal<Person>(); public String setAndGet(String name){ threadLocal.set(new Person(name)); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return threadLocal.get().name; } public static void main(String[] args) { ThreadLocalThree threadLocal=new ThreadLocalThree(); new Thread(()->System.out.println("t1:"+threadLocal.setAndGet("arron")),"t1").start(); new Thread(()->System.out.println("t2:"+threadLocal.setAndGet("tony")),"t2").start(); } } 運行結果: t1:arron t2:tony


- ThreadLocal被稱為線程局部變量,說白了,他就是線程工作內存的一小塊內存,用於存儲數據。
- 那么,ThreadLocal.set()、ThreadLocal.get()方法,就相當於把數據存儲於線程本地,取也是在本地內存讀取。就不會像synchronized需要頻繁的修改主內存的數據,再把數據復制到工作內存,也大大提高訪問效率。
- 回到最開始的舉例,也就等價於mabatis、hibernate為什么要使用threadlocal來存儲session?
- 作用一:因為線程間的數據交互是通過工作內存與主存的頻繁讀寫完成通信,然而存儲於線程本地內存,提高訪問效率,避免線程阻塞造成cpu吞吐率下降。
- 作用二:在多線程中,每一個線程都需要維護session,輕易完成對線程獨享資源的操作。
三、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); }
- 一個線程對應一個ThreadLocalMap ,可以存儲多個ThreadLocal對象。
- ThreadLocal對象作為key、獨享數據作為value。
- ThreadLocalMap可參考HashMap,在ThreadMap里面存在Entry數組也就是一個Entry一個鍵值對。
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(); }
- 一個線程對應一個ThreadLocalMap,get()就是當前線程獲取自己的ThreadLocalMap。
- 線程根據使用那一小塊的threadlocal,根據ThreadLocal對象作為key,去獲取存儲於ThreadLocalMap中的值。
四、ThreadLocal為什么會導致內存泄漏?
- Key使用強引用:也就是上述說的情況,引用ThreadLocal的對象被回收了,ThreadLocal的引用ThreadLocalMap的Key為強引用並沒有被回收,如果不手動回收的話,ThreadLocal將不會回收那么將導致內存泄漏。
- Key使用弱引用:引用的ThreadLocal的對象被回收了,ThreadLocal的引用ThreadLocalMap的Key為弱引用,如果內存回收,那么將ThreadLocalMap的Key將會被回收,ThreadLocal也將被回收。value在ThreadLocalMap調用get、set、remove的時候就會被清除。
- 比較兩種情況,我們可以發現:由於
ThreadLocalMap
的生命周期跟Thread
一樣長,如果都沒有手動刪除對應key
,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal
不會內存泄漏,對應的value
在下一次ThreadLocalMap
調用set
,get
,remove
的時候會被清除。
ThreadLocal提供的方法

對於ThreadLocal而言,常用的方法,就是get/set/initialValue方法。
我們先來看一個例子

運行結果

很顯然,在這里,並沒有通過ThreadLocal達到線程隔離的機制,可是ThreadLocal不是保證線程安全的么?這是什么鬼?
雖然,ThreadLocal讓訪問某個變量的線程都擁有自己的局部變量,但是如果這個局部變量都指向同一個對象呢?這個時候ThreadLocal就失效了。仔細觀察下圖中的代碼,你會發現,threadLocal在初始化時返回的都是同一個對象a!
看一看ThreadLocal源碼
我們直接看最常用的set操作:



你會看到,set需要首先獲得當前線程對象Thread;
然后取出當前線程對象的成員變量ThreadLocalMap;
如果ThreadLocalMap存在,那么進行KEY/VALUE設置,KEY就是ThreadLocal;
如果ThreadLocalMap沒有,那么創建一個;
說白了,當前線程中存在一個Map變量,KEY是ThreadLocal,VALUE是你設置的值。

這里其實揭示了ThreadLocalMap里面的數據存儲結構,從上面的代碼來看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。
ThreadLocalMap.Entry:

在JAVA里面,存在強引用、弱引用、軟引用、虛引用。這里主要談一下強引用和弱引用。
強引用,就不必說了,類似於:
A a = new A();
B b = new B();
考慮這樣的情況:
C c = new C(b);
b = null;
考慮下GC的情況。要知道b被置為null,那么是否意味着一段時間后GC工作可以回收b所分配的內存空間呢?答案是否定的,因為即便b被置為null,但是c仍然持有對b的引用,而且還是強引用,所以GC不會回收b原先所分配的空間!既不能回收利用,又不能使用,這就造成了內存泄露。
那么如何處理呢?
可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)
分析到這里,我們可以得到:

這里我們思考一個問題:ThreadLocal使用到了弱引用,是否意味着不會存在內存泄露呢?
首先來說,如果把ThreadLocal置為null,那么意味着Heap中的ThreadLocal實例不在有強引用指向,只有弱引用存在,因此GC是可以回收這部分空間的,也就是key是可以回收的。但是value卻存在一條從Current Thread過來的強引用鏈。因此只有當Current Thread銷毀時,value才能得到釋放。
因此,只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間內不會被回收的,就發生了我們認為的內存泄露。最要命的是線程對象不被回收的情況,比如使用線程池的時候,線程結束是不會銷毀的,再次使用的,就可能出現內存泄露。
那么如何有效的避免呢?
事實上,在ThreadLocalMap中的set/getEntry方法中,會對key為null(也即是ThreadLocal為null)進行判斷,如果為null的話,那么是會對value置為null的。我們也可以通過調用ThreadLocal的remove方法進行釋放!