面試官:知道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的作用是每一個線程創建一個副本,我們使用一個例子來驗證一下:


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

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

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

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

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

這時候最好ThreadLocal,因為ThreadLocal在每個線程中對連接會創建一個副本,且在線程內部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執行性能。是不是很好用。

以上主要是講解了一個基本的案例,然後還分析了為什麼在數據庫連接的時候會使用ThreadLocal。下面我們從源碼的角度來分析一下,ThreadLocal的工作原理。

三、ThreadLocal源碼分析

在最開始的例子中,只給出了兩個方法也就是get和set方法,其實還有幾個需要我們注意。

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

方法這麼多,我們主要來看set,然後就能認識到整體的ThreadLocal了:

1、set方法

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

從set方法我們可以看到,首先獲取到了當前線程t,然後調用getMap獲取ThreadLocalMap,如果map存在,則將當前線程對象t作為key,要存儲的對象作為value存到map裡面去。如果該Map不存在,則初始化一個。

OK,到這一步了,相信你會有幾個疑惑了,ThreadLocalMap是什麼,getMap方法又是如何實現的。帶著這些問題,繼續往下看。先來看ThreadLocalMap。

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

我們可以看到ThreadLocalMap其實就是ThreadLocal的一個靜態內部類,裡面定義了一個Entry來保存數據,而且還是繼承的弱引用。在Entry內部使用ThreadLocal作為key,使用我們設置的value作為value。

還有一個getMap

<code>1ThreadLocalMapgetMap(Threadt){
2returnt.threadLocals;

3}/<code>

調用當期線程t,返回當前線程t中的成員變量threadLocals。而threadLocals其實就是ThreadLocalMap。

2、get方法

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

通過上面ThreadLocal的介紹相信你對這個方法能夠很好的理解了,首先獲取當前線程,然後調用getMap方法獲取一個ThreadLocalMap,如果map不為null,那就使用當前線程作為ThreadLocalMap的Entry的鍵,然後值就作為相應的的值,如果沒有那就設置一個初始值。

如何設置一個初始值呢?

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

原理很簡單

3、remove方法

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

從我們的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嘛?談談你對它的理解?

上面這張圖詳細的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的關係。

1、Thread中有一個map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我們自己設定的。

3、ThreadLocal是一個弱引用,當為null時,會被當成垃圾回收

4、重點來了,突然我們ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此時我們的ThreadLocalMap生命週期和Thread的一樣,它不會回收,這時候就出現了一個現象。那就是ThreadLocalMap的key沒了,但是value還在,這就造成了內存洩漏。

解決辦法:使用完ThreadLocal後,執行remove操作,避免出現內存溢出情況。


分享到:


相關文章: