- 在每個(gè)Thread對(duì)象里都有一個(gè)ThreadLocalMap對(duì)象,鍵值是ThreadLocal,ThreadLocalMap這個(gè)對(duì)象只能通過ThreadLocal來操作,不能通過Thread操作。
- 在往ThreadLocal對(duì)象里set值時(shí),會(huì)初始化Thread的ThreadLocalMap對(duì)象,然后往里面put值,鍵值就是ThreadLocal對(duì)象。取值時(shí)從當(dāng)前線程Thread里取得ThreadLocalMap對(duì)象,ThreadLocal做鍵去get值。
- 每個(gè)ThreadLocal只能存儲(chǔ)一個(gè)對(duì)象,即如果想存儲(chǔ)對(duì)個(gè)對(duì)象的話那么需要定義多個(gè)ThreadLocal。
- 總結(jié):ThreadLocal并不是解決共享對(duì)象線程安全的,其實(shí)往ThreadLocal中set或initialValue中的對(duì)象并不是共享的,基本上是每個(gè)線程都new一個(gè)放進(jìn)去。如果你真的放共享對(duì)象進(jìn)去,那么在get的時(shí)候返回的仍然是共享對(duì)象,還是會(huì)有線程安全問題。話說誰又會(huì)傻了吧唧的把共享對(duì)象往ThreadLocal里扔呢?
- 由于放進(jìn)去的是每個(gè)線程對(duì)應(yīng)的一個(gè)實(shí)例,因此在用的時(shí)候完全不用擔(dān)心線程安全。同時(shí)能帶著你放進(jìn)去的實(shí)例對(duì)象到處跑。比如在MVC系統(tǒng)中,你在Controller層放一個(gè)對(duì)象到ThreadLocal中,那么在DAO和Service中都可以取到,比用方法參數(shù)傳遞優(yōu)雅多了。但是有一個(gè)前提:即是在同一個(gè)線程中完成所有操作的,如果不是那么就要小心了。
- 一般ThreadLocal對(duì)象都是static的,意味著ThreadLocal不會(huì)被gc。
- 為什么不在ThreadLocal里維護(hù)一個(gè)Map,以線程Thread作為key呢?我想原因如下:
- 在ThreadLocal中維護(hù)Map,那么這個(gè)Map會(huì)變得很大,要經(jīng)常rehash,加鎖等等,同時(shí)Thread基本上是朝生熄滅的對(duì)象,這樣Map如何remove這些對(duì)象就是一個(gè)大問題。而放在Thread中,在Thread結(jié)束后自動(dòng)由jvm來做gc。同時(shí)Thread中的Map基本變化很小。
- 如果是線程池,那么線程不會(huì)被gc,這時(shí)這個(gè)Map和其中的對(duì)象一直存在,知道這個(gè)線程再次被使用,再次往ThreadLocal里set值把以前的對(duì)象給覆蓋掉。
這個(gè)Map是弱引用,可能是防止萬一ThreadLocal不是static的,在ThreadLocal對(duì)象不可達(dá)后不能被gc吧。不過如果ThreadLocal是靜態(tài)的話,那弱引用還會(huì)被gc嗎?那么這個(gè)Map中的value是不是一直不會(huì)被gc ?????????
在一個(gè)線程中創(chuàng)建新線程,當(dāng)前線程就是新線程的parent Thread,如果當(dāng)前線程中有InheritableThreadLocal,那么InheritableThreadLocal中的值會(huì)被新線程繼承,同時(shí)可以在重寫InheritableThreadLocal的childValue方法,讓子線程覆蓋父線程的值。
其實(shí)InheritableThreadLocal和ThreadLocal一樣,每個(gè)線程中都threadLocals和inheritableThreadLocals這2個(gè)獨(dú)立的變量,如果在線程中使用ThreadLocal,那么就會(huì)創(chuàng)建threadLocals變量,如果在線程中使用InheritableThreadLocal,那么就會(huì)創(chuàng)建inheritableThreadLocals變量。只是inheritableThreadLocas可以被子線程繼承,同時(shí)如果在子線程中再創(chuàng)建新線程的話,inheritableThreadLocals中的值會(huì)一直被繼承下去。
源碼解析:
- ThreadLocal中有一個(gè)static類型的AtomicInteger,每new一個(gè)ThreadLocal都會(huì)增加,這個(gè)AtomicInteger決定這個(gè)ThreadLocal對(duì)象在Thread的ThreadLocalMap中處于什么位置。
- ThreadLocalMap底層用Entry數(shù)組實(shí)現(xiàn),Entry繼承了WeakReference,WeakReference對(duì)象會(huì)在GC時(shí)會(huì)嘗試回收。Entry并不是鏈表形式(HashMap中的Entry是鏈表,2個(gè)Entry是不同的東西)。Entry是對(duì)ThreadLocal對(duì)象的弱引用,就是說ThreadLocal被jvm判定為weakily reachable就可能會(huì)回收掉ThreadLocal對(duì)象,這樣設(shè)計(jì)的考慮是在長(zhǎng)時(shí)間運(yùn)行的線程中如果有N多個(gè)ThreadLocal局部變量存放進(jìn)了ThreadLocalMap中,這些ThreadLocal對(duì)象在一定時(shí)間內(nèi)必須要GC掉,因此設(shè)計(jì)成弱引用。往Entry數(shù)組中set值如果發(fā)送hash沖突,ThreadLocalMap是往后(到達(dá)最后位置自動(dòng)跳到0位置繼續(xù))自動(dòng)找Entry中沒元素的位置(叫做slot槽位)存放。在get的時(shí)候,如果查到的位置的key和當(dāng)前的ThreadLocal不相等會(huì)繼續(xù)往后找。
- 在getEntry和setEntry的時(shí)候會(huì)去檢查Entry數(shù)組中是否有stable Entry,如果有則把對(duì)應(yīng)slot置null,并rehash后面的元素直到一個(gè)為null的元素,因我后面不為Null的元素有可能是因?yàn)閔ash沖突放置在后面的slot中,因此需要rehash,而遇到null元素,那么肯定null之后的元素是別的正常元素,不用rehash。
- setEntry
- ThreadLocalMap中的所有操作都沒加鎖,因?yàn)椴豢赡苡卸嗑€程來操作當(dāng)前線程中的ThreadLocalMap,當(dāng)前線程的ThreadLocalMap只對(duì)ThreadLocal可見,其他線程通過ThreadLocal只能看到自己的ThreadLocalMap,是看不到其他線程的ThreadLocalMap的。不存在并發(fā)問題。
- ThreadLocalMap發(fā)生hash沖突,是放到nextIndex的slot中,而不是Entry鏈表,因?yàn)镋ntry是弱引用,隨時(shí)可能被GC。