關于ThreadLocal的用法和說明及注意事項
ThreadLocal
ThreadLocal是用于解決Java并發(fā)安全性問題的一個類。
其主要作用是防止不同線程中的數(shù)據(jù)沖突。
原理圖
如下:

原理說明
創(chuàng)建一個ThreadLocal<V>類的對象,默認會在每一個線程中都開啟一小片區(qū)域,該片區(qū)域可以理解為kay value格式的(實質上是在Thread中有內部類ThreadLocalMap,每聲明了一個ThreadLocal,就相當于在這個ThreadLocalMap中設置了一個<key,value>,因為線程是相互獨立的,所以ThreadLocalMap也是獨立的),ThreadLocalMap中以ThreadLocal實例引用的變量名為key,V為value。
每一個V都是線程獨有的!
使用
ThreadLocal類接口很簡單,只有4個方法:
• void set(Object value)
- 設置當前線程的線程局部變量的值。
• public Object get()
- 該方法返回當前線程所對應的線程局部變量。
• public void remove()
- 將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。
- 需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。
• protected Object initialValue()
- 返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。
- 這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。
- ThreadLocal中的缺省實現(xiàn)直接返回一個null。
實例!
public final static ThreadLocal<String> threadLocal= new ThreadLocal<String>();
threadLocal代表一個能夠存放String類型的ThreadLocal對象。
此時不論什么一個線程能夠并發(fā)訪問這個變量,對它進行寫入、讀取操作,都是線程安全的。
注意!?。?/h2>
ThreadLocal如果應用不妥當會導致內存泄漏。
先來說下什么是內存泄漏和內存溢出,內存泄漏是指某個變量申請了內存的資源,但是引用釋放了,這樣就導致占用著內存卻不能訪問到(俗話叫占著茅坑不拉屎!);
內存溢出是指某個變量在申請內存空間資源的時候需要的空間大于實際的空間,即為內存空間不足了(人太多坑不夠了?。?/p>
如圖解:

當寫下 o=null時,只是表示o不再指向堆中object的對象實例,不代表這個對象實例不存在了。
下面來說明下Java中創(chuàng)建引用的幾種方法
- 強引用就是指在程序代碼之中普遍存在的,類似“Object obj=new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象實例。
- 軟引用是用來描述一些還有用但并非必需的對象。對于軟引用關聯(lián)著的對象,在系統(tǒng)將要發(fā)生內存溢出異常之前,將會把這些對象實例列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。在JDK 1.2之后,提供了SoftReference類來實現(xiàn)軟引用。
- 弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯(lián)的對象實例只能生存到下一次垃圾收集發(fā)生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯(lián)的對象實例。在JDK 1.2之后,提供了WeakReference類來實現(xiàn)弱引用。
- 虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象實例是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯(lián)的唯一目的就是能在這個對象實例被收集器回收時收到一個系統(tǒng)通知。在JDK 1.2之后,提供了PhantomReference類來實現(xiàn)虛引用。
這里只舉一個軟引用的例子:
SoftReference<String> ref = new SoftReference<String>("Hello world");這樣就設置了 ref 對內存中 "Hello world"的軟引用。
ThreadLocal產生內存泄漏的原因
根據(jù)我們前面對ThreadLocal的分析,我們可以知道每個Thread 擁有一個 ThreadLocalMap,這個映射表的 key 是 ThreadLocal實例本身,value 是真正需要存儲的 Object,也就是說 ThreadLocal 本身并不存儲值,它只是作為一個 key 來讓線程從 ThreadLocalMap 獲取 value。
仔細觀察ThreadLocalMap,這個map是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對象在 GC 時會被回收。

圖中的虛線表示弱引用。
這樣,當把threadlocal變量置為null以后,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收。這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而這塊value永遠不會被訪問到了,所以存在著內存泄露。
只有當前thread結束以后,current thread就不會存在棧中,強引用斷開,Current Thread、Map value將全部被GC回收。最好的做法是不在需要使用ThreadLocal變量后,都調用它的remove()方法,清除數(shù)據(jù)。
所以回到我們前面的實驗場景,場景3中,雖然線程池里面的任務執(zhí)行完畢了,但是線程池里面的5個線程會一直存在直到JVM退出,我們set了線程的localVariable變量后沒有調用localVariable.remove()方法,導致線程池里面的5個線程的threadLocals變量里面的new LocalVariable()實例沒有被釋放。
其實考察ThreadLocal的實現(xiàn),我們可以看見,無論是get()、set()在某些時候,調用了expungeStaleEntry方法用來清除Entry中Key為null的Value,但是這是不及時的,也不是每次都會執(zhí)行的,所以一些情況下還是會發(fā)生內存泄露。只有remove()方法中顯式調用了expungeStaleEntry方法。
從表面上看內存泄漏的根源在于使用了弱引用,但是另一個問題也同樣值得思考:為什么使用弱引用而不是強引用?
下面我們分兩種情況討論
- key 使用強引用:對ThreadLocal對象實例的引用被置為null了,但是ThreadLocalMap還持有這個ThreadLocal對象實例的強引用,如果沒有手動刪除,ThreadLocal的對象實例不會被回收,導致Entry內存泄漏。
- key 使用弱引用:對ThreadLocal對象實例的引用被被置為null了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal的對象實例也會被回收。value在下一次ThreadLocalMap調用set,get,remove都有機會被回收。
比較兩種情況,我們可以發(fā)現(xiàn):
由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障。
因此,ThreadLocal內存泄漏的根源是:
由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
ArrayList源碼探秘之Java動態(tài)數(shù)組的實現(xiàn)
這篇文章將帶大家從ArrayList源碼來探秘一下Java動態(tài)數(shù)組的實現(xiàn),文中的示例代碼講解詳細,對我們深入了解JavaScript有一定的幫助,需要的可以參考一下2023-08-08
Spring Security實現(xiàn)動態(tài)路由權限控制方式
這篇文章主要介紹了Spring Security實現(xiàn)動態(tài)路由權限控制方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring Boot實現(xiàn)跨域訪問實現(xiàn)代碼
本文通過實例代碼給大家介紹了Spring Boot實現(xiàn)跨域訪問的知識,然后在文中給大家介紹了spring boot 服務器端設置允許跨域訪問 的方法,感興趣的朋友一起看看吧2017-07-07
springcloud gateway網關服務啟動報錯的解決
這篇文章主要介紹了springcloud gateway網關服務啟動報錯的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
java中如何使用BufferedImage判斷圖像通道順序并轉RGB/BGR
這篇文章主要介紹了java中如何BufferedImage判斷圖像通道順序并轉RGB/BGR的相關資料,需要的朋友可以參考下2017-03-03

