在聊ThreadLocal之前,首先咱們得先聊一下,強引用,軟引用,弱引用與虛引用的問題。
1.強引用
Object o = new Object();這裡的o就是強引用,如果引用一直存在,該對象就不會被回收。
2.軟引用
A a = new A();
SoftReference<A> soft = new SoftReference<A>(a);
如果通過-Xmx設定最大的堆為3M,上面的soft對象占用2M,後面你再new一個大小為2M的對象,你會發現這個soft對象會自動被回收掉。這就是軟引用,當堆記憶體不夠配置設定空間的時候,會被自動回收。
應用場景是:緩存
3.弱引用
B b = new B();
WeakReference<B> weak = new WeakReference<B>(b);
直接說結論吧,弱引用 隻要jvm發生垃圾回收,就會被回收。那麼應用場景是什麼呢?這就和我們今天講的ThreadLocal有關系了,主要是防止一些特定情況下發生記憶體洩露而使用的。具體什麼特定情況,下面再說。
4.虛引用
C c = new C();
Queue queue = new Queue();
PhantomReference<C> phantom = PhantomReference<C>(c,queue);
phantom.get();
你會發現phantom.get()得到的值是null,這就是虛引用,那麼可能就會有疑問,一new出來就為null,有什麼意義呢?其實你發現後面還有一個隊列,這個隊列就表示将這個虛引用放入到該隊列之中,然後起到一個标志性的作用,告訴計算機這裡有一個直接記憶體需要被回收。
例如上圖,jvm中的一個引用直接指向堆外記憶體的區域,那麼如果按照正常的java對象回收,jvm隻會回收jvm中堆的對象,那麼咱們要想回收堆外的對象,就得告訴jvm讓jvm告訴計算機去回收,說白了,這是一種特殊情況,是以需要一個标志,虛引用就是起到該作用。
強,軟,弱,虛引用都講完了,現在再來看ThreadLocal
D d = new D();
ThreadLocal<D> tl = new ThreadLocal<D>();
tl.set(d);
以上最後一行代碼發生了什麼很關鍵:
set方法不難發現,先取目前線程的TheadLocalMap,然後把this也就是目前的tl對象作為key,然後d為value值。再往下看set方法:
是new了一個Entry對象,并且通過構造函數,存進entry裡面了。那麼這個Entry又是什麼呢?繼續往下看:
這裡會發現兩個問題,上面說的存進ThreadLocalMap并不是Map的子類,而是一個對象,然後通過該對象裡面的Entry對象來存儲key和value,而且這裡Entry又繼承了一個弱引用對象,并且在構造函數中将key值傳了進去,再說明白一點,這個key最後就變成了一個弱引用對象了,在jvm發生垃圾回收的時候就會被回收。
那麼為什麼要這樣呢?
假如我們的key是強引用,那麼如果tl=null的時候這個key也不會被回收,應該thread還存在那麼Entry也存在,這樣就有記憶體洩露的風險,因為有的線程是全天24小時在跑的,而這裡如果是弱引用,那麼在垃圾回收的時候就會自動被清除掉。
那麼還有一個問題,雖然這個key是被回收了,但是這個value值還是個強引用,如果線程不死,那麼也将一直存在,并且它的key是null,故還是會引起記憶體洩露問題,這也就是我們常說的ThreadLocal記憶體洩露。
如何解決?顯示的調用remove方法就可以,其實在set和get方法的時候ThreadLocal都會自動清掉key為null值的entry。
最後說一下ThreadLocal的使用場景,舉一個例子如果A方法調用B方法,B方法又調用C方法,如果有一個特定的參數,需要從A傳到C,最常用的就是加形參,但是有個ThreadLocal我們就可以把這個參數存進去,然後在C中使用即可。如spring的事務@Transactional中開啟事務的connection對象就是存在ThreadLocal中。