在java的多線程模塊中,ThreadLocal是經常被提問到的一個知識點,提問的方式有很多種,可能是循序漸進也可能是就像我的題目那樣,因此只有理解透徹了,不管怎麼問,都能遊刃有餘。
這篇文章主要從以下幾個角度來分析理解
1、ThreadLocal是什麼
2、ThreadLocal怎麼用
3、ThreadLocal源碼分析
4、ThreadLocal內存洩漏問題
下面我們帶著這些問題,一點一點揭開ThreadLocal的面紗。若有不正之處請多多諒解,並歡迎批評指正。以下源碼均基於jdk1.8。
一、ThreadLocal是什麼
從名字我們就可以看到ThreadLocal叫做線程變量,意思是ThreadLocal中填充的變量屬於當前線程,該變量對其他線程而言是隔離的。ThreadLocal為變量在每個線程中都創建了一個副本,那麼每個線程可以訪問自己內部的副本變量。
從字面意思來看非常容易理解,但是從實際使用的角度來看,就沒那麼容易了,作為一個面試常問的點,使用場景那也是相當的豐富:
1、在進行對象跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束。
2、線程間數據隔離
3、進行事務操作,用於存儲線程事務信息。
4、數據庫連接,Session會話管理。
現在相信你已經對ThreadLocal有一個大致的認識了,下面我們看看如何用?
二、ThreadLocal怎麼用
既然ThreadLocal的作用是每一個線程創建一個副本,我們使用一個例子來驗證一下:
從結果我們可以看到,每一個線程都有各自的local值,我們設置了一個休眠時間,就是為了另外一個線程也能夠及時的讀取當前的local值。
這就是TheadLocal的基本使用,是不是非常的簡單。那麼為什麼會在數據庫連接的時候使用的比較多呢?
上面是一個數據庫連接的管理類,我們使用數據庫的時候首先就是建立數據庫連接,然後用完了之後關閉就好了,這樣做有一個很嚴重的問題,如果有1個客戶端頻繁的使用數據庫,那麼就需要建立多次鏈接和關閉,我們的服務器可能會吃不消,怎麼辦呢?如果有一萬個客戶端,那麼服務器壓力更大。
這時候最好ThreadLocal,因為ThreadLocal在每個線程中對連接會創建一個副本,且在線程內部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執行性能。是不是很好用。
以上主要是講解了一個基本的案例,然後還分析了為什麼在數據庫連接的時候會使用ThreadLocal。下面我們從源碼的角度來分析一下,ThreadLocal的工作原理。
三、ThreadLocal源碼分析
在最開始的例子中,只給出了兩個方法也就是get和set方法,其實還有幾個需要我們注意。
方法這麼多,我們主要來看set,然後就能認識到整體的ThreadLocal了:
1、set方法
從set方法我們可以看到,首先獲取到了當前線程t,然後調用getMap獲取ThreadLocalMap,如果map存在,則將當前線程對象t作為key,要存儲的對象作為value存到map裡面去。如果該Map不存在,則初始化一個。
OK,到這一步了,相信你會有幾個疑惑了,ThreadLocalMap是什麼,getMap方法又是如何實現的。帶著這些問題,繼續往下看。先來看ThreadLocalMap。
我們可以看到ThreadLocalMap其實就是ThreadLocal的一個靜態內部類,裡面定義了一個Entry來保存數據,而且還是繼承的弱引用。在Entry內部使用ThreadLocal作為key,使用我們設置的value作為value。
還有一個getMap
<code>1ThreadLocalMapgetMap(Threadt){
2returnt.threadLocals;
3}/<code>
調用當期線程t,返回當前線程t中的成員變量threadLocals。而threadLocals其實就是ThreadLocalMap。
2、get方法
通過上面ThreadLocal的介紹相信你對這個方法能夠很好的理解了,首先獲取當前線程,然後調用getMap方法獲取一個ThreadLocalMap,如果map不為null,那就使用當前線程作為ThreadLocalMap的Entry的鍵,然後值就作為相應的的值,如果沒有那就設置一個初始值。
如何設置一個初始值呢?
原理很簡單
3、remove方法
從我們的map移除即可。
OK,其實內部源碼很簡單,現在我們總結一波
(1)每個Thread維護著一個ThreadLocalMap的引用
(2)ThreadLocalMap是ThreadLocal的內部類,用Entry來進行存儲
(3)ThreadLocal創建的副本是存儲在自己的threadLocals中的,也就是自己的ThreadLocalMap。
(4)ThreadLocalMap的鍵值為ThreadLocal對象,而且可以有多個threadLocal變量,因此保存在map中
(5)在進行get之前,必須先set,否則會報空指針異常,當然也可以初始化一個,但是必須重寫initialValue()方法。
(6)ThreadLocal本身並不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value。
OK,現在從源碼的角度上不知道你能理解不,對於ThreadLocal來說關鍵就是內部的ThreadLocalMap。
四、ThreadLocal其他幾個注意的點
只要是介紹ThreadLocal的文章都會幫大家認識一個點,那就是內存洩漏問題。我們先來看下面這張圖。
上面這張圖詳細的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的關係。
1、Thread中有一個map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我們自己設定的。
3、ThreadLocal是一個弱引用,當為null時,會被當成垃圾回收
4、重點來了,突然我們ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此時我們的ThreadLocalMap生命週期和Thread的一樣,它不會回收,這時候就出現了一個現象。那就是ThreadLocalMap的key沒了,但是value還在,這就造成了內存洩漏。
解決辦法:使用完ThreadLocal後,執行remove操作,避免出現內存溢出情況。
閱讀更多 Java的架構師技術棧 的文章