以前覺(jué)得魯迅先生的“我家門口有兩棵樹(shù)一棵是棗樹(shù),另一棵也是棗樹(shù)”完完全全是在湊字?jǐn)?shù)。不過(guò)最近把這個(gè)句式一帶入到“我的賬戶里有兩只股票,一只是綠的霹陡,另一只也是綠的”,馬上就懂了止状,不愧是大文豪烹棉,悲憫的氛圍感一下就出來(lái)了。
上文介紹了security的用戶信息保存在ThreadLocal中导俘,那我們這篇文章聊下ThreadLocal
上節(jié)回顧
??上篇文章主要是從具體實(shí)現(xiàn)層面來(lái)分析峦耘,基于源碼和調(diào)用邏輯的分析聚焦于‘術(shù)’,我們?cè)購(gòu)摹ā膶用媛帽。瑩Q個(gè)視角看下為什么要這么做辅髓,加深理解;
??我們部署的web應(yīng)用少梁,從tomcat——>security——>Ctrl——>Serveice——>Dao洛口,我們需要用到用戶信息的地方很多,如果換做你自己想法實(shí)現(xiàn)
1凯沪、最簡(jiǎn)單的就是透?jìng)鞯谘妫瑥念^到尾的方法都加上User這個(gè)對(duì)象,需要時(shí)獲取妨马,這樣做的缺點(diǎn)顯而易見(jiàn):
- 你不知道哪些方法要使用用戶信息挺举,為了后面萬(wàn)一需要使用,得把所有方法的入?yún)⒁还赡X的加上
- 寫業(yè)務(wù)代碼的人還要關(guān)注這個(gè)方法需不需要獲取用戶信息烘跺,導(dǎo)致所有接口入?yún)⑷哂?/li>
- 后期修改變更極為痛苦
2湘纵、統(tǒng)一存儲(chǔ),然后提供工具類獲取滤淳,這種方法比上一個(gè)優(yōu)雅梧喷,但是存在一個(gè)問(wèn)題是多個(gè)線程訪問(wèn)同一個(gè)資源,存在竟態(tài)條件,會(huì)有線程安全問(wèn)題铺敌,那就得加鎖汇歹,上鎖的話性能就不行了
3、如果可以將用戶信息進(jìn)行本地化存儲(chǔ)偿凭,讓每個(gè)線程都有屬于自己的本地資源产弹,避免多線程之間的共享。這樣ThreadLocal就應(yīng)運(yùn)而生
ThreadLocal初介紹
我們先看Thread類
public class Thread implements Runnable {
···
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */(開(kāi)始學(xué)英語(yǔ)了笔喉,與此線程相關(guān)的線程本地值取视。這map 是由ThreadLocal 類進(jìn)行維護(hù))
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.(與此線程相關(guān)的InheritableThreadLocal值硝皂。這map 是由InheritableThreadLocal 類進(jìn)行維護(hù))
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
···
??可以看到ThreadLocalMap 是Thread類的一個(gè)屬性常挚;但是這些屬性ThreadLocal有啥關(guān)系,怎么使用稽物,我們先擱置奄毡,把后面看了再來(lái)解決
??可以看到ThreadLocalMap是ThreadLocal的內(nèi)部類,然后該內(nèi)部類里面又存在一個(gè)內(nèi)部類Entry贝或,這個(gè)Entry理念和HashMap的Entry類似吼过;可以看到ThreadLocalMap里面把我們需要存儲(chǔ)的東西放到Entry[]數(shù)組table里面,即我們的用戶信息是放到一個(gè)對(duì)象數(shù)組里面的咪奖,最終存儲(chǔ)在Object value這個(gè)屬性中盗忱;
我把上面這段話再捋一捋,把整個(gè)過(guò)程翻譯下:
我們保存用戶信息的時(shí)候羊赵,在security里面是運(yùn)行的代碼是 contextHolder.set(context); contextHolder就是一個(gè)threadLocal 對(duì)象趟佃,這個(gè)然后contextHolder調(diào)用的set方法如下:public void set(T value) { //獲取到當(dāng)前線程 Thread t = Thread.currentThread(); //根據(jù)線程獲取當(dāng)前線程的ThreadLocalMap對(duì)象 ThreadLocalMap map = getMap(t); if (map != null) //設(shè)置進(jìn)去的key為ThreadLocal; value為我們想要保存的用戶數(shù)據(jù) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public T get() { //獲取到當(dāng)前線程 Thread t = Thread.currentThread(); //根據(jù)線程獲取當(dāng)前線程的ThreadLocalMap對(duì)象 ThreadLocalMap map = getMap(t); if (map != null) { //this就是指代當(dāng)前的這個(gè)threadlocal對(duì)象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //找不到的情況下昧捷,調(diào)用初始化方法闲昭,返回初始值 return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal深入
弱引用&內(nèi)存泄露
??從圖上可知,Entry是繼承了弱引用的類靡挥,但是Entry本身不是弱引用序矩,里面那個(gè)key才是,將key(即當(dāng)前threadlocal對(duì)象)存在當(dāng)前父類的referent屬性中跋破,value存放我們想要存儲(chǔ)的值簸淀;
- 什么情況下會(huì)存在內(nèi)存泄露毒返?
??拿線程池舉例:線程池中的線程生命周期比較長(zhǎng)租幕,線程引用到線程對(duì)象的強(qiáng)引用鏈會(huì)一直存在,但是圖片上面那條threadlocal的引用隨著方法的出棧后饿悬,該條強(qiáng)引用鏈就沒(méi)有了令蛉,這一時(shí)刻分析下threadlocal的
的引用鏈路,只存在一條了,就是放入到Entry-key里面存在一條弱引用鏈珠叔,然后發(fā)生gc蝎宇,由于key是弱引用,所以
threadLocal 對(duì)象會(huì)被回收祷安,這個(gè)時(shí)候Entry里面的key沒(méi)了姥芥,沒(méi)有key,那value肯定訪問(wèn)不到汇鞭,這個(gè)時(shí)候Entry對(duì)象占用的內(nèi)存區(qū)域無(wú)法被釋放凉唐,造成內(nèi)存泄露。 - 那為啥要設(shè)計(jì)為弱引用而不是強(qiáng)引用呢霍骄?
??這個(gè)是沒(méi)有明確答案的台囱,只能自己揣摩設(shè)計(jì)思路,我的理解是threadlocal放入到Entry里面的key如果是強(qiáng)引用读整,使用完了簿训,開(kāi)發(fā)人員沒(méi)有釋放,會(huì)造成更大的內(nèi)存泄露米间,兩權(quán)相害取其輕强品,既然都會(huì)泄露,所以稍微選擇影響小點(diǎn)的屈糊,所以改為弱引用的榛;另外代碼里面多處:set,擴(kuò)容等都涉及清除掉Entry key為空的情況逻锐,反向印證了作者是知道這一點(diǎn)夫晌,且為之做了彌補(bǔ)措施
擴(kuò)容
@TODO
Q & A
Q:為什么threadlocalMap要設(shè)計(jì)到Thread線程里面,而不是直接將threadlocal設(shè)計(jì)為一個(gè)公用的map谦去,map-key為線程對(duì)象慷丽,map-value為我們想存儲(chǔ)的值,豈不美哉鳄哭?
A:這樣多個(gè)線程訪問(wèn)threadlocalMap要糊,就又回到集合下Map的思路了,會(huì)產(chǎn)生線程安全問(wèn)題妆丘,同時(shí)加鎖會(huì)導(dǎo)致性能損耗锄俄;
Q:為什么value不為弱引用?
A:Entry中的value如果弱引用勺拣,那么只要GC奶赠,value就會(huì)被清理掉,這時(shí)如果通過(guò)threadlocal對(duì)象查對(duì)應(yīng)的值药有,是null毅戈;
Q:threadlocal有什么好處苹丸?
A:一是可以避免競(jìng)爭(zhēng),各自有一份副本苇经;二是可以方便線程傳參赘理,比如登錄用戶信息;
Q:threadlocal使用場(chǎng)景扇单?
A:保存數(shù)據(jù)庫(kù)鏈接商模;如果一個(gè)請(qǐng)求中涉及多個(gè) DAO 操作,而如果這些DAO中的Connection都是獨(dú)立的話蜘澜,就沒(méi)有辦法完成一個(gè)事務(wù)施流。但是如果DAO 中的 Connection 是從 ThreadLocal 中獲得的(意味著都是同一個(gè)對(duì)象), 那么這些 DAO 就會(huì)被納入到同一個(gè) Connection 之下鄙信。