天天看點

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

文章目錄

  • 一、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的作用是每一個線程建立一個副本,我們使用一個例子來驗證一下:

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

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

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

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

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

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

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

三、ThreadLocal源碼分析

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

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

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

1、set方法

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

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

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

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

我們可以看到ThreadLocalMap其實就是ThreadLocal的一個靜态内部類,裡面定義了一個Entry來儲存資料,而且還是繼承的弱引用。在Entry内部使用ThreadLocal作為key,使用我們設定的value作為value。

還有一個getMap

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}
           

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

2、get方法

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

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

如何設定一個初始值呢?

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、ThreadLocal其他幾個注意的點

原理很簡單

3、remove方法

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、ThreadLocal怎麼用三、ThreadLocal源碼分析四、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的文章都會幫大家認識一個點,那就是記憶體洩漏問題。我們先來看下面這張圖。

JAVA多線程(八)-ThreadLocal一、ThreadLocal是什麼二、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操作,避免出現記憶體溢出情況。

轉載自:https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc

繼續閱讀