天天看點

面試官:知道ThreadLocal嘛?談談你對它的了解?

在java的多線程子產品中,threadlocal是經常被提問到的一個知識點,提問的方式有很多種,可能是循序漸進也可能是就像我的題目那樣,是以隻有了解透徹了,不管怎麼問,都能遊刃有餘。

這篇文章主要從以下幾個角度來分析了解,以下源碼基于jdk1.8。

1、threadlocal是什麼

2、threadlocal怎麼用

3、threadlocal源碼分析

4、threadlocal記憶體洩漏問題

下面我們帶着這些問題,一點一點揭開threadlocal的面紗。若有不正之處請多多諒解,并歡迎批評指正。

從名字我們就可以看到threadlocal叫做線程變量,意思是threadlocal中填充的變量屬于目前線程,該變量對其他線程而言是隔離的。threadlocal為變量在每個線程中都建立了一個副本,那麼每個線程可以通路自己内部的副本變量。

從字面意思來看非常容易了解,但是從實際使用的角度來看,就沒那麼容易了,作為一個面試常問的點,使用場景那也是相當的豐富:

1、在進行對象跨層傳遞的時候,使用threadlocal可以避免多次傳遞,打破層次間的限制。

2、線程間資料隔離

3、進行事務操作,用于存儲線程事務資訊。

4、資料庫連接配接,session會話管理。

現在相信你已經對threadlocal有一個大緻的認識了,下面我們看看如何用?

既然threadlocal的作用是每一個線程建立一個副本,我們使用一個例子來驗證一下:

從結果我們可以看到,每一個線程都有各自的local值,我們設定了一個休眠時間,就是為了另外一個線程也能夠及時的讀取目前的local值。

這就是theadlocal的基本使用,是不是非常的簡單。那麼為什麼會在資料庫連接配接的時候使用的比較多呢?

上面是一個資料庫連接配接的管理類,我們使用資料庫的時候首先就是建立資料庫連接配接,然後用完了之後關閉就好了,這樣做有一個很嚴重的問題,如果有1個用戶端頻繁的使用資料庫,那麼就需要建立多次連結和關閉,我們的伺服器可能會吃不消,怎麼辦呢?如果有一萬個用戶端,那麼伺服器壓力更大。

這時候最好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

調用當期線程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還在,這就造成了記憶體洩漏。