
ThreadLocal是一個(gè)關(guān)于創(chuàng)建線程局部變量的類。通常情況下,我們創(chuàng)建的變量是可以被任何一個(gè)線程訪問并修改的。而使用ThreadLocal創(chuàng)建的變量只能被當(dāng)前線程訪問,其他線程則無法訪問和修改。ThreadLocal在設(shè)計(jì)之初就是為解決并發(fā)問題而提供一種方案,每個(gè)線程維護(hù)一份自己的數(shù)據(jù),達(dá)到線程隔離的效果。
在現(xiàn)在的系統(tǒng)設(shè)計(jì)中,前后端分離已基本成為常態(tài),分離之后如何獲取用戶信息就成了一件麻煩事,通常在用戶登錄后, 用戶信息會(huì)保存在Session或者Token中。這個(gè)時(shí)候,我們?nèi)绻褂贸R?guī)的手段去獲取用戶信息會(huì)很費(fèi)勁,拿Session來說,我們要在接口參數(shù)中加上HttpServletRequest對(duì)象,然后調(diào)用 getSession方法,且每一個(gè)需要用戶信息的接口都要加上這個(gè)參數(shù),才能獲取Session,這樣實(shí)現(xiàn)就很麻煩了。
在實(shí)際的系統(tǒng)設(shè)計(jì)中,我們肯定不會(huì)采用上面所說的這種方式,而是使用ThreadLocal,我們會(huì)選擇在攔截器的業(yè)務(wù)中, 獲取到保存的用戶信息,然后存入ThreadLocal,那么當(dāng)前線程在任何地方如果需要拿到用戶信息都可以使用ThreadLocal的get()方法 (異步程序中ThreadLocal是不可靠的)
【資料圖】
在Spring的Web項(xiàng)目中,我們通常會(huì)將業(yè)務(wù)分為Controller層,Service層,Dao層, 我們都知道??@Autowired??注解默認(rèn)使用單例模式,那么不同請(qǐng)求線程進(jìn)來之后,由于Dao層使用單例,那么負(fù)責(zé)數(shù)據(jù)庫連接的Connection也只有一個(gè), 如果每個(gè)請(qǐng)求線程都去連接數(shù)據(jù)庫,那么就會(huì)造成線程不安全的問題,Spring是如何解決這個(gè)問題的呢?
在Spring項(xiàng)目中Dao層中裝配的Connection肯定是線程安全的,其解決方案就是采用ThreadLocal方法,當(dāng)每個(gè)請(qǐng)求線程使用Connection的時(shí)候, 都會(huì)從ThreadLocal獲取一次,如果為null,說明沒有進(jìn)行過數(shù)據(jù)庫連接,連接后存入ThreadLocal中,如此一來,每一個(gè)請(qǐng)求線程都保存有一份 自己的Connection。于是便解決了線程安全問題
在登錄攔截器中將用戶信息寫入,后續(xù)使用時(shí)方便取值
你是否有這樣的疑惑?為什么可以直接拿到?對(duì)象存放在哪里?存在什么問題?
在 get() 方法中也會(huì)獲取到當(dāng)前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則把獲取 key 為當(dāng)前 ThreadLocal 的值;否則調(diào)用 setInitialValue() 方法返回初始值,并保存到新創(chuàng)建的 ThreadLocalMap 中。
調(diào)用set時(shí),直接調(diào)用set(T value) 方法中,首先獲取當(dāng)前線程,然后在獲取到當(dāng)前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則將 value 保存到 ThreadLocalMap 中,并用當(dāng)前 ThreadLocal 作為 key;否則創(chuàng)建一個(gè) ThreadLocalMap 并給到當(dāng)前線程,然后保存 value。
ThreadLocalMap 相當(dāng)于一個(gè) HashMap,是真正保存值的地方map的set,如果map為空,則創(chuàng)建一個(gè)
initialValue() 是 ThreadLocal 的初始值,默認(rèn)返回 null,子類可以重寫改方法,用于設(shè)置 ThreadLocal 的初始值。
ThreadLocal 還有一個(gè) remove() 方法,用來移除當(dāng)前 ThreadLocal 對(duì)應(yīng)的值。同樣也是同過當(dāng)前線程的 ThreadLocalMap 來移除相應(yīng)的值。
getMap拿到了什么?在 set,get,initialValue 和 remove 方法中都會(huì)獲取到當(dāng)前線程,然后通過當(dāng)前線程獲取到 ThreadLocalMap,如果 ThreadLocalMap 為 null,則會(huì)創(chuàng)建一個(gè) ThreadLocalMap,并給到當(dāng)前線程
此處t是Thread,直接可以“點(diǎn)”拿到這個(gè)map每個(gè)Thread對(duì)象內(nèi)部都維護(hù)了一個(gè)ThreadLocalMap這樣一個(gè)ThreadLocal的Map,可以存放若干個(gè)ThreadLocal
在使用 ThreadLocal 類型變量進(jìn)行相關(guān)操作時(shí),都會(huì)通過當(dāng)前線程獲取到 ThreadLocalMap 來完成操作。每個(gè)線程的 ThreadLocalMap 是屬于線程自己的,ThreadLocalMap 中維護(hù)的值也是屬于線程自己的。這就保證了 ThreadLocal 類型的變量在每個(gè)線程中是獨(dú)立的,在多線程環(huán)境下不會(huì)相互影響。
1)有可能導(dǎo)致內(nèi)存泄漏,使用完畢后,需要remove
在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除無效 Entry 的操作,這樣做是為了降低內(nèi)存泄漏發(fā)生的可能。Entry 中的 key 使用了弱引用的方式,這樣做是為了降低內(nèi)存泄漏發(fā)生的概率,但不能完全避免內(nèi)存泄漏。
假設(shè) Entry 的 key 沒有使用弱引用的方式,而是使用了強(qiáng)引用:由于 ThreadLocalMap 的生命周期和當(dāng)前線程一樣長(zhǎng),那么當(dāng)引用 ThreadLocal 的對(duì)象被回收后,由于 ThreadLocalMap 還持有 ThreadLocal 和對(duì)應(yīng) value 的強(qiáng)引用,ThreadLocal 和對(duì)應(yīng)的 value 是不會(huì)被回收的,這就導(dǎo)致了內(nèi)存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 沒有被回收而導(dǎo)致的內(nèi)存泄漏,但是此時(shí) value 仍然是無法回收的,依然會(huì)導(dǎo)致內(nèi)存泄漏。
ThreadLocalMap 已經(jīng)考慮到這種情況,并且有一些防護(hù)措施:在調(diào)用 ThreadLocal 的 get(),set() 和 remove() 的時(shí)候都會(huì)清除當(dāng)前線程 ThreadLocalMap 中所有 key 為 null 的 value。這樣可以降低內(nèi)存泄漏發(fā)生的概率。所以我們?cè)谑褂?ThreadLocal 的時(shí)候,每次用完 ThreadLocal 都調(diào)用 remove() 方法,清除數(shù)據(jù),防止內(nèi)存泄漏。
2)使用線程池時(shí),父子線程傳遞慎用,因?yàn)槌跏蓟瘯r(shí)機(jī)為線程創(chuàng)建時(shí)
3)針對(duì)2有什么方案可以解決?TransmittableThreadLocal源碼地址:?https://github.com/alibaba/transmittable-thread-local??>詳解:?https://www.jianshu.com/p/e0774f965aa3??>
標(biāo)簽: 內(nèi)存泄漏 數(shù)據(jù)庫連接