小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Java多線程編程-看了這篇關(guān)于ThreadLocal的原理應(yīng)該透徹了

 CHEP 2017-10-23
Java后端技術(shù) 2017-10-21

ThreadLocal可以說是筆試面試的???,每逢面試基本都會(huì)問到,關(guān)于ThreadLocal的原理以及不正當(dāng)?shù)氖褂迷斐傻腛OM內(nèi)存溢出的問題,值得花時(shí)間仔細(xì)研究一下其原理。這一篇主要學(xué)習(xí)一下ThreadLocal的原理,在下一篇會(huì)深入理解一下OOM內(nèi)存溢出的原理和最佳實(shí)踐。

ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個(gè)“本地線程”。其實(shí),ThreadLocal并不是一個(gè)Thread,而是Thread的一個(gè)局部變量,也許把它命名為更容易讓人理解一些。

當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。

ThreadLocal 的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度。

從線程的角度看,目標(biāo)變量就像是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思。

一、ThreadLocal全部方法和內(nèi)部類

ThreadLocal全部方法和內(nèi)部類結(jié)構(gòu)如下:

ThreadLocal公有的方法就四個(gè),分別為:get、set、remove、intiValue:

也就是說我們平時(shí)使用的時(shí)候關(guān)心的是這四個(gè)方法。

ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?

其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單:在ThreadLocal類中有一個(gè)static聲明的Map,用于存儲(chǔ)每一個(gè)線程的變量副本,Map中元素的鍵為線程對(duì)象,而值對(duì)應(yīng)線程的變量副本。我們自己就可以提供一個(gè)簡(jiǎn)單的實(shí)現(xiàn)版本:

運(yùn)行結(jié)果:

雖然上面的代碼清單中的這個(gè)ThreadLocal實(shí)現(xiàn)版本顯得比較簡(jiǎn)單粗,但其目的主要在與呈現(xiàn)JDK中所提供的ThreadLocal類在實(shí)現(xiàn)上的思路。

二、ThreadLocal源碼分析

1、線程局部變量在Thread中的位置

既然是線程局部變量,那么理所當(dāng)然就應(yīng)該存儲(chǔ)在自己的線程對(duì)象中,我們可以從 Thread 的源碼中找到線程局部變量存儲(chǔ)的地方:

我們可以看到線程局部變量是存儲(chǔ)在Thread對(duì)象的屬性中,而屬性是一個(gè)對(duì)象。

ThreadLocalMap為ThreadLocal的靜態(tài)內(nèi)部類,如下圖所示:

2、Thread和ThreadLocalMap的關(guān)系

Thread和ThreadLocalMap的關(guān)系,先看下邊這個(gè)簡(jiǎn)單的圖,可以看出Thread中的就是ThreadLocal中的ThreadLocalMap:

到這里應(yīng)該大致能夠感受到上述三者之間微妙的關(guān)系,再看一個(gè)復(fù)雜點(diǎn)的圖:

可以看出每個(gè)實(shí)例都有一個(gè)。在上圖中的一個(gè)Thread的這個(gè)ThreadLocalMap中分別存放了3個(gè)Entry,默認(rèn)一個(gè)ThreadLocalMap初始化了16個(gè)Entry,每一個(gè)Entry對(duì)象存放的是一個(gè)ThreadLocal變量對(duì)象。

再看一張網(wǎng)絡(luò)上的圖片,應(yīng)該可以更好的理解,如下圖:

這里的Map其實(shí)是ThreadLocalMap。

3、ThreadLocalMap與WeakReference

從字面上就可以看出這是一個(gè)保存對(duì)象的map(其實(shí)是以它為Key),不過是經(jīng)過了兩層包裝的ThreadLocal對(duì)象:

(1)第一層包裝是使用將對(duì)象變成一個(gè)弱引用的對(duì)象;

(2)第二層包裝是定義了一個(gè)專門的類 Entry 來擴(kuò)展:

類 Entry 很顯然是一個(gè)保存map鍵值對(duì)的實(shí)體,為key, 要保存的線程局部變量的值為。調(diào)用的的構(gòu)造函數(shù),表示將對(duì)象轉(zhuǎn)換成弱引用對(duì)象,用做key。

4、ThreadLocalMap 的構(gòu)造函數(shù)

可以看出,ThreadLocalMap這個(gè)map的實(shí)現(xiàn)是使用一個(gè)數(shù)組來保存鍵值對(duì)的實(shí)體,初始大小為16,自己實(shí)現(xiàn)了如何從到 value 的映射:

使用一個(gè)的原子屬性,通過每次增加,然后取得在數(shù)組中的索引。

總的來說,ThreadLocalMap是一個(gè)類似HashMap的集合,只不過自己實(shí)現(xiàn)了尋址,也沒有HashMap中的put方法,而是set方法等區(qū)別。

三、ThreadLocal的set方法

由于每個(gè)thread實(shí)例都有一個(gè)ThreadLocalMap,所以在進(jìn)行set的時(shí)候,首先根據(jù)Thread.currentThread()獲取當(dāng)前線程,然后根據(jù)當(dāng)前線程t,調(diào)用getMap(t)獲取ThreadLocalMap對(duì)象, 如果是第一次設(shè)置值,ThreadLocalMap對(duì)象是空值,所以會(huì)進(jìn)行初始化操作,即調(diào)用方法:

即是調(diào)用上述的構(gòu)造方法進(jìn)行構(gòu)造,這里僅僅是初始化了16個(gè)元素的引用數(shù)組,并沒有初始化16個(gè) Entry 對(duì)象。而是一個(gè)線程中有多少個(gè)線程局部對(duì)象要保存,那么就初始化多少個(gè) Entry 對(duì)象來保存它們。

到了這里,我們可以思考一下,為什么要這樣實(shí)現(xiàn)了。

1、為什么要用 ThreadLocalMap 來保存線程局部對(duì)象呢?

原因是一個(gè)線程擁有的的局部對(duì)象可能有很多,這樣實(shí)現(xiàn)的話,那么不管你一個(gè)線程擁有多少個(gè)局部變量,都是使用同一個(gè) ThreadLocalMap 來保存的,ThreadLocalMap 中的初始大小是16。超過容量的2/3時(shí),會(huì)擴(kuò)容。

然后在回到如果map不為空的情況,會(huì)調(diào)用方法,我們看到是以當(dāng)前 thread 的引用為 key, 獲得,然后調(diào)用保存進(jìn):

可以看到,方法為每個(gè)Thread對(duì)象都創(chuàng)建了一個(gè)ThreadLocalMap,并且將value放入ThreadLocalMap中,ThreadLocalMap作為Thread對(duì)象的成員變量保存。那么可以用下圖來表示ThreadLocal在存儲(chǔ)value時(shí)的關(guān)系。

2、了解了set方法的大致原理之后,我們?cè)谘芯恳欢纬绦蛉缦拢?/strong>

這樣的話就相當(dāng)于一個(gè)線程依附了三個(gè)ThreadLocal對(duì)象,執(zhí)行完最后一個(gè)set方法之后,調(diào)試過程如下:

可以看到table(Entry集合)中有三個(gè)對(duì)象,對(duì)象的值就是我們?cè)O(shè)置的三個(gè)threadLocal的對(duì)象值;

3、如果在修改一下代碼,修改為兩個(gè)線程:

這樣的話,可以看到運(yùn)行調(diào)試圖如下:

然后更改到Thread2,查看,由于多線程,線程1運(yùn)行到上圖情況,線程2運(yùn)行到下圖情況,也可以看出他們是不同的ThreadLocalMap:

那如果多個(gè)線程,只設(shè)置一個(gè)ThreadLocal變量那,結(jié)果可想而知,這里不再贅述!

另外,有一點(diǎn)需要提示一下,代碼如下:

運(yùn)行結(jié)果:

可以看到,在這個(gè)線程中的ThreadLocal變量的值始終是只有一個(gè)的,即以前的值被覆蓋了的!這里是因?yàn)镋ntry對(duì)象是以該ThreadLocal變量的引用為key的,所以多次賦值以前的值會(huì)被覆蓋,特此注意!

到這里應(yīng)該可以清楚了的了解Thread、ThreadLocal和ThreadLocalMap之間的關(guān)系了!

四、ThreadLocal的get方法

經(jīng)過上述set方法的分析,對(duì)于get方法應(yīng)該理解起來輕松了許多,首先獲取ThreadLocalMap對(duì)象,由于ThreadLocalMap使用的當(dāng)前的ThreadLocal作為key,所以傳入的參數(shù)為this,然后調(diào)用方法,通過這個(gè)key構(gòu)造索引,根據(jù)索引去table(Entry數(shù)組)中去查找線程本地變量,根據(jù)下邊找到Entry對(duì)象,然后判斷Entry對(duì)象e不為空并且e的引用與傳入的key一樣則直接返回,如果找不到則調(diào)用方法。調(diào)用表示直接散列到的位置沒找到,那么順著hash表遞增(循環(huán))地往下找,從i開始,一直往下找,直到出現(xiàn)空的槽為止。

五、ThreadLocal的內(nèi)存回收

ThreadLocal 涉及到的兩個(gè)層面的內(nèi)存自動(dòng)回收:

(1)在 ThreadLocal 層面的內(nèi)存回收:

當(dāng)線程死亡時(shí),那么所有的保存在的線程局部變量就會(huì)被回收,其實(shí)這里是指線程Thread對(duì)象中的會(huì)被回收,這是顯然的。

(2)ThreadLocalMap 層面的內(nèi)存回收:

如果線程可以活很長(zhǎng)的時(shí)間,并且該線程保存的線程局部變量有很多(也就是 Entry 對(duì)象很多),那么就涉及到在線程的生命期內(nèi)如何回收 ThreadLocalMap 的內(nèi)存了,不然的話,Entry對(duì)象越多,那么ThreadLocalMap 就會(huì)越來越大,占用的內(nèi)存就會(huì)越來越多,所以對(duì)于已經(jīng)不需要了的線程局部變量,就應(yīng)該清理掉其對(duì)應(yīng)的Entry對(duì)象。

使用的方式是,Entry對(duì)象的key是WeakReference 的包裝,當(dāng)ThreadLocalMap 的,已經(jīng)被占用達(dá)到了三分之二時(shí)(也就是線程擁有的局部變量超過了10個(gè)) ,就會(huì)嘗試回收 Entry 對(duì)象,我們可以看到方法中有下面的代碼:

cleanSomeSlots 就是進(jìn)行回收內(nèi)存:

六、ThreadLocal可能引起的OOM內(nèi)存溢出問題簡(jiǎn)要分析

我們知道ThreadLocal變量是維護(hù)在Thread內(nèi)部的,這樣的話只要我們的線程不退出,對(duì)象的引用就會(huì)一直存在。當(dāng)線程退出時(shí),Thread類會(huì)進(jìn)行一些清理工作,其中就包含ThreadLocalMap,Thread調(diào)用exit方法如下:

但是,當(dāng)我們使用線程池的時(shí)候,就意味著當(dāng)前線程未必會(huì)退出(比如固定大小的線程池,線程總是存在的)。如果這樣的話,將一些很大的對(duì)象設(shè)置到ThreadLocal中(這個(gè)很大的對(duì)象實(shí)際保存在Thread的threadLocals屬性中),這樣的話就可能會(huì)出現(xiàn)內(nèi)存溢出的情況。

一種場(chǎng)景就是說如果使用了線程池并且設(shè)置了固定的線程,處理一次業(yè)務(wù)的時(shí)候存放到ThreadLocalMap中一個(gè)大對(duì)象,處理另一個(gè)業(yè)務(wù)的時(shí)候,又一個(gè)線程存放到ThreadLocalMap中一個(gè)大對(duì)象,但是這個(gè)線程由于是線程池創(chuàng)建的他會(huì)一直存在,不會(huì)被銷毀,這樣的話,以前執(zhí)行業(yè)務(wù)的時(shí)候存放到ThreadLocalMap中的對(duì)象可能不會(huì)被再次使用,但是由于線程不會(huì)被關(guān)閉,因此無法釋放Thread 中的ThreadLocalMap對(duì)象,造成內(nèi)存溢出。

也就是說,ThreadLocal在沒有線程池使用的情況下,正常情況下不會(huì)存在內(nèi)存泄露,但是如果使用了線程池的話,就依賴于線程池的實(shí)現(xiàn),如果線程池不銷毀線程的話,那么就會(huì)存在內(nèi)存泄露。所以我們?cè)谑褂镁€程池的時(shí)候,使用ThreadLocal要格外小心!

七、總結(jié)

通過源代碼可以看到每個(gè)線程都可以獨(dú)立修改屬于自己的副本而不會(huì)互相影響,從而隔離了線程和線程.避免了線程訪問實(shí)例變量發(fā)生安全問題. 同時(shí)我們也能得出下面的結(jié)論:

(1)ThreadLocal只是操作Thread中的ThreadLocalMap對(duì)象的集合;

(2)ThreadLocalMap變量屬于線程的內(nèi)部屬性,不同的線程擁有完全不同的ThreadLocalMap變量;

(3)線程中的ThreadLocalMap變量的值是在ThreadLocal對(duì)象進(jìn)行set或者get操作時(shí)創(chuàng)建的;

(4)使用當(dāng)前線程的ThreadLocalMap的關(guān)鍵在于使用當(dāng)前的ThreadLocal的實(shí)例作為key來存儲(chǔ)value值;

(5) ThreadLocal模式至少?gòu)膬蓚€(gè)方面完成了數(shù)據(jù)訪問隔離,即縱向隔離(線程與線程之間的ThreadLocalMap不同)和橫向隔離(不同的ThreadLocal實(shí)例之間的互相隔離);

(6)一個(gè)線程中的所有的局部變量其實(shí)存儲(chǔ)在該線程自己的同一個(gè)map屬性中;

(7)線程死亡時(shí),線程局部變量會(huì)自動(dòng)回收內(nèi)存;

(8)線程局部變量時(shí)通過一個(gè) Entry 保存在map中,該Entry 的key是一個(gè) WeakReference包裝的ThreadLocal, value為線程局部變量,key 到 value 的映射是通過:來完成的;

(9)當(dāng)線程擁有的局部變量超過了容量的2/3(沒有擴(kuò)大容量時(shí)是10個(gè)),會(huì)涉及到ThreadLocalMap中Entry的回收;

對(duì)于多線程資源共享的問題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問,而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問而互不影響。

參考文章:

2、http://www.cnblogs.com/digdeep/p/4510875.html


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多