ThreadLocal最全詳解(萬字圖文總結(jié))
大家好,我是mikechen。
ThreadLocal是實現(xiàn)Java并發(fā)編程非常重要的一個組件,也是大廠喜歡考察的內(nèi)容,下面我就全面來詳解ThreadLocal@mikechen
ThreadLocal
ThreadLocal提供了線程的本地變量,是 Java 中用于實現(xiàn)線程局部變量的類,它提供了線程內(nèi)部的獨立變量。
即即每個線程都有一個獨立的"變量副本",不會與其他線程的"變量副本"產(chǎn)生沖突。
如下圖所示:
圖片
每個線程都有自己的獨立資源,可以通過 ThreadLocal 對象訪問它自己的獨立變量。
ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的,也就是說該變量是當前線程獨有的變量。
ThreadLocal主要用于:解決多線程并發(fā)時訪問共享變量的問題,主要是做數(shù)據(jù)隔離。
ThreadLocal原理
ThreadLocal原理:ThreadLocal相當于維護了一個map,key就是當前的線程,value就是需要存儲的對象。
這個 Map 不是直接使用 HashMap ,而是 ThreadLocal 實現(xiàn)的一個叫做 ThreadLocalMap 的靜態(tài)內(nèi)部類,用來存變量。
它的大概結(jié)構(gòu)如下圖所示:
圖片
ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的核心存儲結(jié)構(gòu),類似于 HashMap,但設(shè)計上有所不同:
ThreadLocalMap 是 ThreadLocal 的內(nèi)部靜態(tài)類,是一個自定義的哈希表,專門用于存儲與 ThreadLocal 關(guān)聯(lián)的數(shù)據(jù)。
每個線程都持有一個 ThreadLocalMap 實例,以存儲它的 ThreadLocal 變量和對應(yīng)的值。
ThreadLocalMap是一個Map,key是ThreadLocal,value是Object。
Entry 類
除此之外,ThreadLocalMap內(nèi)部持有一個 Entry[] 類型的數(shù)組 table,每個數(shù)組成員都是一個鍵值對,Entry數(shù)組是真正承載數(shù)據(jù)的地方。
ThreadLocalMap.Entry 繼承自 Java 標準庫中的 WeakReference<ThreadLocal<?>>,它的核心結(jié)構(gòu)如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//key就是一個弱引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry繼承自WeakReference,每個Entry 的 key 都是一個 ThreadLocal 對象的弱引用,Java 中的弱引用允許對一個對象的引用在沒有強引用的情況下,被垃圾回收器回收。
value 是Object 類型,是強引用,ThreadLocalMap 中可以包含多個ThreadLocal對象。
如下圖所示:
圖片
ThreadLocalMap中包含了多個ThreadLocal對象,那么如果一個線程使用多個ThreadLocal對象,ThreadLocalMap如何區(qū)分不同的ThreadLocal呢?
實際上,每一個ThreadLocal對象都包含了一個獨一無二的threadLocalHashCode值,使用這個值就可以在KV數(shù)組中找到對應(yīng)的本地變量。
圖片
key是ThreadLocal對象的弱引用,之所以使用 WeakReference 類型作為ThreadLocal對象的引用,是出于垃圾回收考慮。
不過需要注意的是,雖然key值是弱引用,不影響ThreadLocal對象回收,但value值是強引用。
當ThreadLocal被回收,value對象不會被回收,可能會引發(fā)內(nèi)存泄漏。
所以,記得要調(diào)用 remove() 方法,避免內(nèi)存泄露。
ThreadLocal使用
ThreadLocal的用法,這個類提供thread-local變量,這些變量與線程的局部變量不同,每個線程都保存一份改變量的副本,可以通過get、或者set方法訪問。
如下所示:
//創(chuàng)建
private ThreadLocal threadLocal = new ThreadLocal();
//一旦創(chuàng)建了ThreadLocal,就可以使用它的set()方法設(shè)置要存儲在其中的值
threadLocal.set("A thread local value");
//獲取值
String threadLocalValue = (String) threadLocal.get();
//移除一個值
threadLocal.remove();
ThreadLocal提供了線程內(nèi)存儲變量的能力,這些變量不同之處在于每一個線程讀取的變量是對應(yīng)的互相獨立的,通過get和set方法就可以得到當前線程對應(yīng)的值。
由于ThreadLocal里設(shè)置的值,只有當前線程自己看得見,這意味著你不可能通過其他線程為它初始化值,為了彌補這一點,ThreadLocal提供了一個withInitial()方法統(tǒng)一初始化所有線程的ThreadLocal的值。
如下所示:
private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 18);
上述代碼將ThreadLocal的初始值設(shè)置為18,這對全體線程都是可見的。
ThreadLocal應(yīng)用
在通常的業(yè)務(wù)開發(fā)中,ThreadLocal 有以下3種典型的使用場景:
圖片
1.解決線程安全問題
ThreadLocal 用作保存每個線程獨享的對象,為每個線程都創(chuàng)建一個副本,這樣每個線程都可以修改自己所擁有的副本, 而不會影響其他線程的副本,確保了線程安全。
2.代替參數(shù)的顯式傳遞
當我們在寫API接口的時候,通常Controller層會接受來自前端的入?yún)ⅰ?/p>
當這個接口功能比較復(fù)雜的時候,通常情況下,我們會在每個調(diào)用的方法上加上需要傳遞的參數(shù)。
但是,如果我們將參數(shù)存入ThreadLocal中,那么就不用顯式的傳遞參數(shù)了,而是只需要ThreadLocal中獲取即可。
這是因為:使用參數(shù)傳遞造成代碼的耦合度高,使用靜態(tài)全局變量在多線程環(huán)境下不安全,當該對象用ThreadLocal包裝過后,就可以保證在該線程中獨此一份,同時和其他線程隔離。
比如:在Spring的@Transaction事務(wù)聲明的注解中,就使用ThreadLocal保存了當前的Connection對象,避免在本次調(diào)用的不同方法中使用不同的Connection對象。
3.全局存儲用戶信息
可以嘗試使用ThreadLocal替代Session的使用,每個線程擁有獨立的 Session 對象。
當用戶要訪問需要授權(quán)的接口的時候,可以現(xiàn)在攔截器中將用戶的Token存入ThreadLocal中,之后在本次訪問中任何需要用戶用戶信息的都可以直接沖ThreadLocal中拿取數(shù)據(jù)。