1、目錄
一、ThreadLocal是什麼?有什麼用?
二、ThreadLocal源碼簡要總結?
三、ThreadLocal爲何會致使內存泄漏?
2、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
分析:
一、根據預期結果,那ThreadLocal究竟是什麼?
回顧Java內存模型:
在虛擬機中,堆內存用於存儲共享數據(實例對象),堆內存也就是這裏說的主內存。
每一個線程將會在堆內存中開闢一塊空間叫作線程的工做內存,附帶一塊緩存區用於存儲共享數據副本。那麼,共享數據在堆內存當中,線程通訊就是經過主內存爲中介,線程在本地內存讀而且操做完共享變量操做完畢之後,把值寫入主內存。
- ThreadLocal被稱爲線程局部變量,說白了,他就是線程工做內存的一小塊內存,用於存儲數據。
- 那麼,ThreadLocal.set()、ThreadLocal.get()方法,就至關於把數據存儲於線程本地,取也是在本地內存讀取。就不會像synchronized須要頻繁的修改主內存的數據,再把數據複製到工做內存,也大大提升訪問效率。
二、ThreadLocal到底有什麼用?
- 回到最開始的舉例,也就等價於mabatis、hibernate爲何要使用threadlocal來存儲session?
- 做用一:由於線程間的數據交互是經過工做內存與主存的頻繁讀寫完成通訊,然而存儲於線程本地內存,提升訪問效率,避免線程阻塞形成cpu吞吐率降低。
- 做用二:在多線程中,每個線程都須要維護session,輕易完成對線程獨享資源的操做。
總結:
Threadlocal是什麼?在堆內存中,每一個線程對應一塊工做內存,threadlocal就是工做內存的一小塊內存。
Threadlocal有什麼用?threadlocal用於存取線程獨享數據,提升訪問效率。
3、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的東西。
4、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。
總結:
- 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