ThreadLocal 也可以跟蹤一個請求,從接收請求,處理請求,到傳回請求,隻要線程不銷毀,就可以線上程的任何地方,調用這個參數,這是百度二面的題目,參考:
Threadlocal 傳遞參數(百度二面)
總結:
- 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;
}
一、目錄
1、ThreadLocal是什麼?有什麼用?
2、ThreadLocal源碼簡要總結?
3、ThreadLocal為什麼會導緻記憶體洩漏?
二、ThreadLocal是什麼?有什麼用?
引入話題:在并發條件下,如何正确獲得共享資料?舉例:假設有多個使用者需要擷取使用者資訊,一個線程對應一個使用者。在mybatis中,session用于操作資料庫,那麼設定、擷取操作分别是session.set()、session.get(),如何保證每個線程都能正确操作達到想要的結果?
/**
* 回顧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,當一個線程通路共享資料的時候,其他線程堵塞,不再多餘贅述。
通過舉例問題,可能大家又會很疑惑?
mybatis、hibernate是如何實作的呢?
synchronized不會很消耗資源,當成千上萬個操作的時候,承受并發不說,資料傳回延遲如何確定使用者體驗?
ThreadLocal是什麼?有什麼用?
/**
* 談談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
分析:
1、根據預期結果,那ThreadLocal到底是什麼?
回顧Java記憶體模型:
在虛拟機中,堆記憶體用于存儲共享資料(執行個體對象),堆記憶體也就是這裡說的主記憶體。
每個線程将會在堆記憶體中開辟一塊空間叫做線程的工作記憶體,附帶一塊緩存區用于存儲共享資料副本。那麼,共享資料在堆記憶體當中,線程通信就是通過主記憶體為中介,線程在本地記憶體讀并且操作完共享變量操作完畢以後,把值寫入主記憶體。
- ThreadLocal被稱為線程局部變量,說白了,他就是線程工作記憶體的一小塊記憶體,用于存儲資料。
- 那麼,ThreadLocal.set()、ThreadLocal.get()方法,就相當于把資料存儲于線程本地,取也是在本地記憶體讀取。就不會像synchronized需要頻繁的修改主記憶體的資料,再把資料複制到工作記憶體,也大大提高通路效率。
2、ThreadLocal到底有什麼用?
- 回到最開始的舉例,也就等價于mabatis、hibernate為什麼要使用threadlocal來存儲session?
- 作用一:因為線程間的資料互動是通過工作記憶體與主存的頻繁讀寫完成通信,然而存儲于線程本地記憶體,提高通路效率,避免線程阻塞造成cpu吞吐率下降。
- 作用二:在多線程中,每一個線程都需要維護session,輕易完成對線程獨享資源的操作。
Threadlocal是什麼?在堆記憶體中,每個線程對應一塊工作記憶體,threadlocal就是工作記憶體的一小塊記憶體。
Threadlocal有什麼用?threadlocal用于存取線程獨享資料,提高通路效率。
三、ThreadLocal源碼簡要總結?
那有同學可能還是有點雲裡霧裡,感覺還是沒有吃透?那線程内部如何去保證線程獨享資料呢?
在這裡,我隻做簡要總結,若有興趣,可參考文章尾部的文章連結。重點看get、set方法。
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中的值。
回顧一下,我們在單線程中如何使用HashMap的?hashMap根據數組+連結清單來實作HashMap,一個key對應一個value。那麼,我們抽象一下,Threadlocal也相當于在多線程中的一種HashMap用法,相當于對ThradLocal的操作也就如單線程操作一樣。
總之,ThreadLocal就是堆記憶體的一塊小記憶體,它用ThreadLocalMap維護ThreadLocal對象作為key,獨享資料作為value的東西。
四、ThreadLocal為什麼會導緻記憶體洩漏?
synchronized是用時間換空間(犧牲時間)、ThreadLocal是用空間換時間(犧牲空間),為什麼這麼說?
因為synchronized操作資料,隻需要在主存存一個變量即可,就阻塞等共享變量,而ThreadLocal是每個線程都建立一塊小的堆工作記憶體。顯然,印證了上面的說法。
一個線程對應一塊工作記憶體,線程可以存儲多個ThreadLocal。那麼假設,開啟1萬個線程,每個線程建立1萬個ThreadLocal,也就是每個線程維護1萬個ThreadLocal小記憶體空間,而且當線程執行結束以後,假設這些ThreadLocal裡的Entry還不會被回收,那麼将很容易導緻堆記憶體溢出。
怎麼辦?難道JVM就沒有提供什麼解決方案嗎?
ThreadLocal當然有想到,是以他們把ThreadLocal裡的Entry設定為弱引用,當垃圾回收的時候,回收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
那按你這麼說,既然JVM有保障了,還有什麼記憶體洩漏可言?
ThreadLocalMap使用ThreadLocal對象作為弱引用,當垃圾回收的時候,ThreadLocalMap中Key将會被回收,也就是将Key設定為null的Entry。如果線程遲遲無法結束,也就是ThreadLocal對象将一直不會回收,回顧到上面存在很多線程+TheradLocal,那麼也将導緻記憶體洩漏。(記憶體洩露的重點)
其實,在ThreadLocal中,當調用remove、get、set方法的時候,會清除為null的弱引用,也就是回收ThreadLocal。
ThreadLocal提供一個線程(Thread)局部變量,通路到某個變量的每一個線程都擁有自己的局部變量。說白了,ThreadLocal就是想在多線程環境下去保證成員變量的安全。
ThreadLocal提供的方法
ThreadLocal API
對于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。
我們先來看一個例子
demo
運作結果
是你想象中的結果麼?
很顯然,在這裡,并沒有通過ThreadLocal達到線程隔離的機制,可是ThreadLocal不是保證線程安全的麼?這是什麼鬼?
雖然,ThreadLocal讓通路某個變量的線程都擁有自己的局部變量,但是如果這個局部變量都指向同一個對象呢?這個時候ThreadLocal就失效了。仔細觀察下圖中的代碼,你會發現,threadLocal在初始化時傳回的都是同一個對象a!
看一看ThreadLocal源碼
我們直接看最常用的set操作:
set
線程局部變量
createMap
你會看到,set需要首先獲得目前線程對象Thread;
然後取出目前線程對象的成員變量ThreadLocalMap;
如果ThreadLocalMap存在,那麼進行KEY/VALUE設定,KEY就是ThreadLocal;
如果ThreadLocalMap沒有,那麼建立一個;
說白了,目前線程中存在一個Map變量,KEY是ThreadLocal,VALUE是你設定的值。
看一下get操作:
get
這裡其實揭示了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和線程結束這段時間内不會被回收的,就發生了我們認為的記憶體洩露。最要命的是線程對象不被回收的情況,比如使用線程池的時候,線程結束是不會銷毀的,再次使用的,就可能出現記憶體洩露。
參考:ThreadLocal可能引起的記憶體洩露
參考:對ThreadLocal實作原理的一點思考
參考:并發程式設計(四):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
/**
* 談談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
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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
Thread
key
ThreadLocal
value
ThreadLocalMap
set
get
remove