java多線程編程中奇唤,線程安全是個(gè)很重要的概念。在保證線程安全的手段中匹摇,除了同步咬扇、鎖、不變對(duì)象外廊勃,還有一種很重要的方法便是線程封閉技術(shù)懈贺,就是將可變對(duì)象封閉在一個(gè)線程中,同時(shí)只能由一個(gè)線程訪問(wèn)該對(duì)象坡垫。實(shí)現(xiàn)線程封閉的一種常用方法便是使用ThreadLocal類梭灿,這個(gè)類能使線程中的某個(gè)值與保存的對(duì)象關(guān)聯(lián)起來(lái)。ThreadLocal為每個(gè)使用變量的線程都存有一份副本冰悠,因此get總是返回由當(dāng)前線程set的最新值堡妒。那么ThreadLocal如何實(shí)現(xiàn)變量的線程封閉的呢?下面我們通過(guò)對(duì)ThreadLocal的源碼分析來(lái)解讀其實(shí)現(xiàn)原理溉卓。
實(shí)現(xiàn)原理
類的關(guān)系和職責(zé)
ThreadLocal中定義了一個(gè)內(nèi)部靜態(tài)類ThreadLocalMap皮迟,該類是線程本地變量的容器——一個(gè)類似HashMap的容器。這兩個(gè)類再加上Thread類共同配合實(shí)現(xiàn)了線程封閉技術(shù)桑寨。首先伏尼,我們來(lái)看一下這幾類的關(guān)系圖
從圖中我們可看出這幾個(gè)類有如下重要關(guān)系:
Thread持有一個(gè)包級(jí)別的ThreadLocalMap成員變量threadLocals,兩者屬于聚合關(guān)系
ThreadLocal通過(guò)Thread.currentThread()靜態(tài)方法獲得對(duì)當(dāng)前線程的引用依賴
由于ThreadLocal類與Thread類在同一個(gè)包下尉尾,因此ThreadLocal可以自由訪問(wèn)Thread.threadLocals變量爆阶,因此ThreadLocal類則負(fù)責(zé)創(chuàng)建和初始化threadLocals變量
我們自定義的應(yīng)用類ApplicationClass類只依賴ThreadLocal類,并訪問(wèn)該類的公有方法可以寫(xiě)入和讀取線程的本地變量
ThreadLocalMap的key是ThreadLocal對(duì)象沙咏,value是ThreadLocal中存放的變量
圖中類之間的關(guān)系還是比較繞的扰她,但對(duì)于使用者來(lái)說(shuō)相對(duì)簡(jiǎn)單,我們只關(guān)心ThreadLocal類便可以了芭碍。最常用的ThreadLocal類方法public T get()
和public void set(T value)
徒役,還有一個(gè)protected作用域的方法protected T initialValue()
用于初始化變量值。這里我們會(huì)發(fā)現(xiàn)窖壕,ThreadLocal類似于Facade設(shè)計(jì)模式的門(mén)面類忧勿,對(duì)外提供簡(jiǎn)單的接口,對(duì)內(nèi)協(xié)調(diào)Thread和ThreadLocalMap之間的關(guān)系瞻讽。而真正實(shí)現(xiàn)需求核心邏輯的是類Thread和ThreadLocalMap鸳吸。
那么,我們可以定義這么一個(gè)需求:我們需要將一個(gè)或多個(gè)可變對(duì)象的封閉在一個(gè)線程中速勇,使得一個(gè)可變對(duì)象在不進(jìn)行同步的情況下晌砾,同時(shí)只能有一個(gè)線程可以訪問(wèn)該變量,從而實(shí)現(xiàn)線程安全烦磁。對(duì)于這個(gè)需求养匈,我們明確一下三個(gè)類的職責(zé)關(guān)系哼勇,可能更容易理解:
Thread類是使線程封閉的主體,對(duì)象的訪問(wèn)權(quán)限就是封閉在當(dāng)前運(yùn)行的線程中呕乎,只能由當(dāng)前線程訪問(wèn)(這也是為什么會(huì)由Thread類以聚合的關(guān)系持有ThreadLocalMap對(duì)象)
我們的需求是可以實(shí)現(xiàn)多個(gè)可變對(duì)象的線程封閉积担,因此,我們定義了一個(gè)map容器用于存儲(chǔ)多個(gè)可變對(duì)象猬仁,這個(gè)類便是ThreadLocalMap類帝璧,這個(gè)map的key是ThreadLocal對(duì)象,value是可變對(duì)象(也就是那個(gè)被封閉起來(lái)的寶寶)湿刽。
ThreadLocal對(duì)外提供簡(jiǎn)單的方法的烁,同時(shí)由于ThreadLocal和可變對(duì)象是一對(duì)一的關(guān)系,正好可以作為T(mén)hreadLocalMap的key與可變對(duì)象做關(guān)聯(lián)诈闺。
經(jīng)過(guò)以上的分析渴庆,我們會(huì)發(fā)現(xiàn),這個(gè)設(shè)計(jì)非常巧妙买雾,既遵循了類職責(zé)單一的面向?qū)ο笤瓌t,又實(shí)現(xiàn)了我們的需求杨帽,同時(shí)對(duì)使用者非常友好漓穿。
ThreadLocalMap實(shí)現(xiàn)關(guān)鍵點(diǎn)
通過(guò)以上類關(guān)系和職責(zé)的分析,我們知道ThreadLocal的核心業(yè)務(wù)邏輯其實(shí)是在ThreadLocalMap中的注盈,那么晃危,接下來(lái)我們就重點(diǎn)關(guān)注一下ThreadLocalMap實(shí)現(xiàn)中的幾個(gè)關(guān)鍵點(diǎn)。
從代碼中我們發(fā)現(xiàn)老客,ThreadLocalMap其實(shí)就是一個(gè)簡(jiǎn)版的HashMap僚饭,在這個(gè)特定的map中的存儲(chǔ)的是一個(gè)Entry數(shù)組,而ThreadLocalMap中的Entry實(shí)現(xiàn)非常簡(jiǎn)單卻也很特別:
static class ThreadLocalMap {
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// ...
}
這個(gè)Entry是一個(gè)弱引用胧砰,而ThreadLocalMap中的table數(shù)組變量只是持有ThreadLocal對(duì)象的虛引用鳍鸵。這里為什么要這么設(shè)計(jì)呢?我們不妨回憶一下幾種引用的特性尉间。
- 強(qiáng)引用(Strong Reference)就是我們最常見(jiàn)的普通對(duì)象引用偿乖,只要還有強(qiáng)引用指向這個(gè)對(duì)象,就表明對(duì)象還“活著”哲嘲,垃圾收集器不會(huì)碰這種對(duì)象贪薪。對(duì)于一個(gè)普通的對(duì)象,如果沒(méi)有其他的引用關(guān)系眠副,只要超過(guò)了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為 null画切,就是可以被垃圾收集的了,當(dāng)然具體回收時(shí)機(jī)還是要看垃圾收集策略囱怕。
- 軟引用(SoftReference)是一種相對(duì)強(qiáng)引用弱化一些的引用霍弹,可以讓對(duì)象豁免一些垃圾收集毫别,只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象庞萍。JVM 會(huì)確保在拋出 OutOfMemoryError 之前拧烦,清理軟引用指向的對(duì)象。軟引用通常用來(lái)實(shí)現(xiàn)內(nèi)存敏感的緩存钝计,如果還有空閑內(nèi)存恋博,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉私恬,這樣就保證了使用緩存的同時(shí)债沮,不會(huì)耗盡內(nèi)存。
- 弱引用(WeakReference)并不能使對(duì)象被豁免垃圾回收本鸣,僅僅提供一種訪問(wèn)在弱引用狀態(tài)下對(duì)象的途徑疫衩。被弱引用關(guān)聯(lián)的對(duì)象,在垃圾回收時(shí)荣德,如果這個(gè)對(duì)象只被弱引用關(guān)聯(lián)(沒(méi)有任何強(qiáng)引用關(guān)聯(lián)他)闷煤,那么這個(gè)對(duì)象就會(huì)被回收。這就可以用來(lái)構(gòu)建一種沒(méi)有特定約束的關(guān)系涮瞻,比如鲤拿,維護(hù)一種非強(qiáng)制性的映射關(guān)系,如果試圖獲取時(shí)對(duì)象還在署咽,就使用它近顷,否則重新實(shí)例化。它同樣是很多緩存實(shí)現(xiàn)的選擇宁否。
- 虛引用(PhantomReference)不能通過(guò)它訪問(wèn)對(duì)象窒升。幻象引用僅僅是提供了一種確保對(duì)象被 finalize 以后慕匠,做某些事情的機(jī)制饱须,比如,通常用來(lái)做所謂的 Post-Mortem 清理機(jī)制台谊,也有人利用幻象引用監(jiān)控對(duì)象的創(chuàng)建和銷(xiāo)毀冤寿。
這里我們重點(diǎn)關(guān)注一下弱引用的特性:只有弱引用指向的對(duì)象是不影響垃圾收集器對(duì)其回收的。那么青伤,ThreadLocalMap中的Entry為什么定義為T(mén)hreadLocal的弱引用類呢督怜?這里我們不妨反向去思考,假設(shè)這里不用虛引用的實(shí)現(xiàn)狠角,而是類似HashMap中的強(qiáng)引用号杠,會(huì)有什么問(wèn)題嗎?我們知道除了我們自定義的應(yīng)用類持有ThreadLocal的強(qiáng)引用外,還存在一條線程到ThreadLocal的引用鏈條:Thread.threadLocals --> ThreadLocalMap.table --> ThreadLocal對(duì)象姨蟋。在很多應(yīng)用尤其是后端服務(wù)器應(yīng)用中屉凯,會(huì)創(chuàng)建很多線程,并且線程的生命的周期往往比很多對(duì)象生命周期要長(zhǎng)很多眼溶。在使用ThreadLocal的過(guò)程中悠砚,不在引用作用域范圍內(nèi)的ThreadLocal對(duì)象或手動(dòng)置空(threadLocal=null
)的ThreadLocal對(duì)象,是可以被垃圾回收掉的堂飞。而如果在線程到ThreadLocal的引用鏈中是強(qiáng)引用灌旧,那么這個(gè)ThreadLocal對(duì)象的生命周期將伴隨著Thread對(duì)象的存活而一直存在。這其實(shí)是不利于垃圾回收和內(nèi)存利用的绰筛,相當(dāng)于白白浪費(fèi)了ThreadLocal對(duì)象占用的內(nèi)存空間枢泰。因此,為了不影響垃圾收集器對(duì)ThreadLocal變量的回收铝噩,這里Entry的實(shí)現(xiàn)用了WeakReference衡蚂。
從上面代碼分析中,我們也會(huì)了解到為了盡快實(shí)現(xiàn)垃圾收集器對(duì)ThreadLocal以及相關(guān)聯(lián)的對(duì)象的回收骏庸,對(duì)于我們不再使用的ThreadLocal變量毛甲,最好將相關(guān)的強(qiáng)引用置空,以避免內(nèi)存溢出具被。代碼如下示例所示:
// threadLocal是事先定義好的ThreadLocal變量
threadLocal.remove();
threadLocal = null;
應(yīng)用實(shí)例
在一些框架或基礎(chǔ)類庫(kù)中玻募,ThreadLocal的使用還是比較廣泛的。下面就舉幾個(gè)常見(jiàn)的例子:
concurent包中的應(yīng)用
在讀寫(xiě)鎖java.util.concurrent.locks.ReentrantReadWriteLock
實(shí)現(xiàn)中硬猫,ThreadLocal用于記錄當(dāng)前線程持有讀鎖的次數(shù):
/**
* ThreadLocal subclass. Easiest to explicitly define for sake
* of deserialization mechanics.
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* The number of reentrant read locks held by current thread.
* Initialized only in constructor and readObject.
* Removed whenever a thread's read hold count drops to 0.
*/
private transient ThreadLocalHoldCounter readHolds;
spring中的事務(wù)管理
spring中的事務(wù)管理补箍,也大量使用了ThreadLocal改执,代碼如下:
// spring-core中定義的NamedThreadLocal
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
public String toString() {
return this.name;
}
}
// spring-tx包中定義的事務(wù)管理器
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
// ...
}
在spring的事務(wù)管理框架通過(guò)將事務(wù)上下文信息保存在ThreadLocal對(duì)象中啸蜜,實(shí)現(xiàn)了事務(wù)上線文與執(zhí)行線程的關(guān)聯(lián)關(guān)系,從而使事務(wù)管理與數(shù)據(jù)訪問(wèn)服務(wù)解耦辈挂,同時(shí)也保證了多線程環(huán)境下connection的線程安全問(wèn)題衬横。
避免濫用
不過(guò),開(kāi)發(fā)人員也經(jīng)常會(huì)濫用ThreadLocal终蒂,例如將所有全局變量都作為T(mén)hreadLocal對(duì)象蜂林,或者作為一種“隱藏”的傳參手段。但是ThreadLocal也有全局變量的缺點(diǎn)拇泣,它降低了代碼的可重用性噪叙,并在類之間引入隱含的耦合性,因此霉翔,在使用時(shí)需要謹(jǐn)慎睁蕾,必要的情況下再使用。