文章目錄
- 一、ThreadLocal是什麼
- 二、ThreadLocal怎麼用
- 三、ThreadLocal源碼分析
- 四、ThreadLocal其他幾個注意的點
在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
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
調用當期線程t,傳回目前線程t中的成員變量threadLocals。而threadLocals其實就是ThreadLocalMap。
2、get方法
通過上面ThreadLocal的介紹相信你對這個方法能夠很好的了解了,首先擷取目前線程,然後調用getMap方法擷取一個ThreadLocalMap,如果map不為null,那就使用目前線程作為ThreadLocalMap的Entry的鍵,然後值就作為相應的的值,如果沒有那就設定一個初始值。
如何設定一個初始值呢?
原理很簡單
3、remove方法
從我們的map移除即可。
OK,其實内部源碼很簡單,現在我們總結一波
- 每個Thread維護着一個ThreadLocalMap的引用
- ThreadLocalMap是ThreadLocal的内部類,用Entry來進行存儲
- ThreadLocal建立的副本是存儲在自己的threadLocals中的,也就是自己的ThreadLocalMap。
- ThreadLocalMap的鍵值為ThreadLocal對象,而且可以有多個threadLocal變量,是以儲存在map中
- 在進行get之前,必須先set,否則會報空指針異常,當然也可以初始化一個,但是必須重寫initialValue()方法。
- 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操作,避免出現記憶體溢出情況。
轉載自:https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc