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)該類的公有方法可以寫入和讀取線程的本地變量
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ì)模式的門面類,對(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)系,正好可以作為ThreadLocalMap的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)建和銷毀。
這里我們重點(diǎn)關(guān)注一下弱引用的特性:只有弱引用指向的對(duì)象是不影響垃圾收集器對(duì)其回收的。那么,ThreadLocalMap中的Entry為什么定義為ThreadLocal的弱引用類呢?這里我們不妨反向去思考,假設(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)用
在讀寫鎖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,例如將所有全局變量都作為ThreadLocal對(duì)象,或者作為一種“隱藏”的傳參手段。但是ThreadLocal也有全局變量的缺點(diǎn),它降低了代碼的可重用性,并在類之間引入隱含的耦合性,因此,在使用時(shí)需要謹(jǐn)慎,必要的情況下再使用。