谈谈对ThreadLocal的理解?(基于jdk1.8)
在java的多線程模塊中,ThreadLocal是經(jīng)常被提問到的一個知識點,提問的方式有很多種,可能是循序漸進也可能是就像我的題目那樣,因此只有理解透徹了,不管怎么問,都能游刃有余。
這篇文章主要從以下幾個角度來分析理解
1、ThreadLocal是什么
2、ThreadLocal怎么用
3、ThreadLocal源碼分析
4、ThreadLocal內(nèi)存泄漏問題
下面我們帶著這些問題,一點一點揭開ThreadLocal的面紗。若有不正之處請多多諒解,并歡迎批評指正。以下源碼均基于jdk1.8。
一、ThreadLocal是什么
從名字我們就可以看到ThreadLocal叫做線程變量,意思是ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的。ThreadLocal為變量在每個線程中都創(chuàng)建了一個副本,那么每個線程可以訪問自己內(nèi)部的副本變量。
從字面意思來看非常容易理解,但是從實際使用的角度來看,就沒那么容易了,作為一個面試常問的點,使用場景那也是相當?shù)呢S富:
1、在進行對象跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束。
2、線程間數(shù)據(jù)隔離
3、進行事務(wù)操作,用于存儲線程事務(wù)信息。
4、數(shù)據(jù)庫連接,Session會話管理。
現(xiàn)在相信你已經(jīng)對ThreadLocal有一個大致的認識了,下面我們看看如何用?
二、ThreadLocal怎么用
既然ThreadLocal的作用是每一個線程創(chuàng)建一個副本,我們使用一個例子來驗證一下:
從結(jié)果我們可以看到,每一個線程都有各自的local值,我們設(shè)置了一個休眠時間,就是為了另外一個線程也能夠及時的讀取當前的local值。
這就是TheadLocal的基本使用,是不是非常的簡單。那么為什么會在數(shù)據(jù)庫連接的時候使用的比較多呢?
上面是一個數(shù)據(jù)庫連接的管理類,我們使用數(shù)據(jù)庫的時候首先就是建立數(shù)據(jù)庫連接,然后用完了之后關(guān)閉就好了,這樣做有一個很嚴重的問題,如果有1個客戶端頻繁的使用數(shù)據(jù)庫,那么就需要建立多次鏈接和關(guān)閉,我們的服務(wù)器可能會吃不消,怎么辦呢?如果有一萬個客戶端,那么服務(wù)器壓力更大。
這時候最好ThreadLocal,因為ThreadLocal在每個線程中對連接會創(chuàng)建一個副本,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執(zhí)行性能。是不是很好用。
以上主要是講解了一個基本的案例,然后還分析了為什么在數(shù)據(jù)庫連接的時候會使用ThreadLocal。下面我們從源碼的角度來分析一下,ThreadLocal的工作原理。
三、ThreadLocal源碼分析
在最開始的例子中,只給出了兩個方法也就是get和set方法,其實還有幾個需要我們注意。
方法這么多,我們主要來看set,然后就能認識到整體的ThreadLocal了:
1、set方法
從set方法我們可以看到,首先獲取到了當前線程t,然后調(diào)用getMap獲取ThreadLocalMap,如果map存在,則將當前線程對象t作為key,要存儲的對象作為value存到map里面去。如果該Map不存在,則初始化一個。
OK,到這一步了,相信你會有幾個疑惑了,ThreadLocalMap是什么,getMap方法又是如何實現(xiàn)的。帶著這些問題,繼續(xù)往下看。先來看ThreadLocalMap。
我們可以看到ThreadLocalMap其實就是ThreadLocal的一個靜態(tài)內(nèi)部類,里面定義了一個Entry來保存數(shù)據(jù),而且還是繼承的弱引用。在Entry內(nèi)部使用ThreadLocal作為key,使用我們設(shè)置的value作為value。
還有一個getMap
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}調(diào)用當期線程t,返回當前線程t中的成員變量threadLocals。而threadLocals其實就是ThreadLocalMap。
2、get方法
通過上面ThreadLocal的介紹相信你對這個方法能夠很好的理解了,首先獲取當前線程,然后調(diào)用getMap方法獲取一個ThreadLocalMap,如果map不為null,那就使用當前線程作為ThreadLocalMap的Entry的鍵,然后值就作為相應(yīng)的的值,如果沒有那就設(shè)置一個初始值。
如何設(shè)置一個初始值呢?
原理很簡單
3、remove方法
從我們的map移除即可。
OK,其實內(nèi)部源碼很簡單,現(xiàn)在我們總結(jié)一波
(1)每個Thread維護著一個ThreadLocalMap的引用
(2)ThreadLocalMap是ThreadLocal的內(nèi)部類,用Entry來進行存儲
(3)ThreadLocal創(chuàng)建的副本是存儲在自己的threadLocals中的,也就是自己的ThreadLocalMap。
(4)ThreadLocalMap的鍵值為ThreadLocal對象,而且可以有多個threadLocal變量,因此保存在map中
(5)在進行g(shù)et之前,必須先set,否則會報空指針異常,當然也可以初始化一個,但是必須重寫initialValue()方法。
(6)ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value。
OK,現(xiàn)在從源碼的角度上不知道你能理解不,對于ThreadLocal來說關(guān)鍵就是內(nèi)部的ThreadLocalMap。
四、ThreadLocal其他幾個注意的點
只要是介紹ThreadLocal的文章都會幫大家認識一個點,那就是內(nèi)存泄漏問題。我們先來看下面這張圖。
上面這張圖詳細的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的關(guān)系。
1、Thread中有一個map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我們自己設(shè)定的。
3、ThreadLocal是一個弱引用,當為null時,會被當成垃圾回收
4、重點來了,突然我們ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此時我們的ThreadLocalMap生命周期和Thread的一樣,它不會回收,這時候就出現(xiàn)了一個現(xiàn)象。那就是ThreadLocalMap的key沒了,但是value還在,這就造成了內(nèi)存泄漏。
解決辦法:使用完ThreadLocal后,執(zhí)行remove操作,避免出現(xiàn)內(nèi)存溢出情況。
總結(jié)
以上是生活随笔為你收集整理的谈谈对ThreadLocal的理解?(基于jdk1.8)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python学习系列day4-pytho
- 下一篇: springboot前后端分离后权限原理