ThreadLocal是thread local variable(線(xiàn)程局部變量)。線(xiàn)程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線(xiàn)程都提供一個(gè)變量值的副本,是每一個(gè)線(xiàn)程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線(xiàn)程的副本沖突。每個(gè)線(xiàn)程只能看到與自己相聯(lián)系的值,而不知道別的線(xiàn)程可能正在使用或修改它們自己的副本。從線(xiàn)程的角度看,就好像每一個(gè)線(xiàn)程都完全擁有該變量。
ThreadLocal的設(shè)計(jì)
首先看看ThreadLocal的接口:








Object get() ; // 返回當(dāng)前線(xiàn)程的線(xiàn)程局部變量副本
protected Object initialValue(); // 返回該線(xiàn)程局部變量的當(dāng)前線(xiàn)程的初始值
void set(Object value); // 設(shè)置當(dāng)前線(xiàn)程的線(xiàn)程局部變量副本的值
ThreadLocal有3個(gè)方法,其中值得注意的是initialValue(),該方法是一個(gè)protected的方法,顯然是為了子類(lèi)重寫(xiě)而特意實(shí)現(xiàn)的。該方法返回當(dāng)前線(xiàn)程在該線(xiàn)程局部變量的初始值,這個(gè)方法是一個(gè)延遲調(diào)用方法,在一個(gè)線(xiàn)程第1次調(diào)用get()或者set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的確實(shí)實(shí)現(xiàn)直接返回一個(gè)null:
protected Object initialValue() { return null; } |
ThreadLocal是如何做到為每一個(gè)線(xiàn)程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,在ThreadLocal類(lèi)中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線(xiàn)程的變量的副本。比如下面的示例實(shí)現(xiàn):



































這個(gè)實(shí)現(xiàn)的性能不會(huì)很好,因?yàn)槊總€(gè) get()
和 set()
操作都需要 values
映射表上的同步,而且如果多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)同一個(gè) ThreadLocal
,那么將發(fā)生爭(zhēng)用。此外,這個(gè)實(shí)現(xiàn)也是不切實(shí)際的,因?yàn)橛?Thread
對(duì)象做 values
映射表中的關(guān)鍵字將導(dǎo)致無(wú)法在線(xiàn)程退出后對(duì) Thread
進(jìn)行垃圾回收,而且也無(wú)法對(duì)死線(xiàn)程的 ThreadLocal
的特定于線(xiàn)程的值進(jìn)行垃圾回收。
但JDK中的ThreadLocal的實(shí)現(xiàn)總體思路也類(lèi)似于此。
j2EE中的Thread
Web容器中有三個(gè)周期request/Httpsession/application
其中request是客戶(hù)端發(fā)出的一個(gè)請(qǐng)求,這個(gè)request的載體就是一個(gè)線(xiàn)程,實(shí)際等同于一個(gè)線(xiàn)程的生命周期。Request是封裝在線(xiàn)程上面一個(gè)抽象概念。而ThreadLocal則相當(dāng)于多個(gè)線(xiàn)程的一個(gè)共享全局變量存儲(chǔ)地,它里面保存的是和每個(gè)線(xiàn)程相關(guān)的狀態(tài)。所以threadLocal是為線(xiàn)程服務(wù)的,和線(xiàn)程處于一個(gè)底層位置。
正因?yàn)镾pring/Hibernate這些框架對(duì)于狀態(tài)處理短處,所以才只能透過(guò)Web容器的request等狀態(tài)封裝,直接到底層操作與線(xiàn)程同一層次的threadLocal。
當(dāng)一個(gè)線(xiàn)程或request結(jié)束時(shí),threadlocal中的狀態(tài)就沒(méi)有了,所以threadLocal基本類(lèi)似request.setAttribute作用,threadLocal中的對(duì)象狀態(tài)的生命周期等同于request.
ThreadLocal的使用
用 ThreadLocal 實(shí)現(xiàn)每線(xiàn)程 Singleton
線(xiàn)程局部變量常被用來(lái)描繪有狀態(tài)“單子”(Singleton) 或線(xiàn)程安全的共享對(duì)象,或者是通過(guò)把不安全的整個(gè)變量封裝進(jìn) ThreadLocal
,或者是通過(guò)把對(duì)象的特定于線(xiàn)程的狀態(tài)封裝進(jìn) ThreadLocal
。例如,在與數(shù)據(jù)庫(kù)有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)。在系統(tǒng)的每個(gè)方法中都包含一個(gè) Connection
作為參數(shù)是不方便的 — 用“單子”來(lái)訪(fǎng)問(wèn)連接可能是一個(gè)雖然更粗糙,但卻方便得多的技術(shù)。然而,多個(gè)線(xiàn)程不能安全地共享一個(gè) JDBC Connection
。如清單 3 所示,通過(guò)使用“單子”中的 ThreadLocal
,我們就能讓我們的程序中的任何類(lèi)容易地獲取每線(xiàn)程 Connection
的一個(gè)引用。這樣,我們可以認(rèn)為 ThreadLocal
允許我們創(chuàng)建 每線(xiàn)程單子。
清單 3. 把一個(gè) JDBC 連接存儲(chǔ)到一個(gè)每線(xiàn)程 Singleton 中




















任何創(chuàng)建的花費(fèi)比使用的花費(fèi)相對(duì)昂貴些的有狀態(tài)或非線(xiàn)程安全的對(duì)象,例如 JDBC Connection
或正則表達(dá)式匹配器,都是可以使用每線(xiàn)程單子(singleton)技術(shù)的好地方。當(dāng)然,在類(lèi)似這樣的地方,您可以使用其它技術(shù),例如用池,來(lái)安全地管理共享訪(fǎng)問(wèn)。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因?yàn)槌貙?shí)現(xiàn)必須使用同步,以維護(hù)池?cái)?shù)據(jù)結(jié)構(gòu)的完整性,如果所有線(xiàn)程使用同一個(gè)池,那么在有很多線(xiàn)程頻繁地對(duì)池進(jìn)行訪(fǎng)問(wèn)的系統(tǒng)中,程序性能將因爭(zhēng)用而降低。
用 ThreadLocal 簡(jiǎn)化調(diào)試日志紀(jì)錄
其它適合使用 ThreadLocal
但用池卻不能成為很好的替代技術(shù)的應(yīng)用程序包括存儲(chǔ)或累積每線(xiàn)程上下文信息以備稍后檢索之用這樣的應(yīng)用程序。例如,假設(shè)您想創(chuàng)建一個(gè)用于管理多線(xiàn)程應(yīng)用程序調(diào)試信息的工具。您可以用如清單 4 所示的 DebugLogger
類(lèi)作為線(xiàn)程局部容器來(lái)累積調(diào)試信息。在一個(gè)工作單元的開(kāi)頭,您清空容器,而當(dāng)一個(gè)錯(cuò)誤出現(xiàn)時(shí),您查詢(xún)?cè)撊萜饕詸z索這個(gè)工作單元迄今為止生成的所有調(diào)試信息。
清單 4. 用 ThreadLocal 管理每線(xiàn)程調(diào)試日志




































在您的代碼中,您可以調(diào)用 DebugLogger.put()
來(lái)保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發(fā)生了一個(gè)錯(cuò)誤),您能夠容易地檢索與某個(gè)特定線(xiàn)程相關(guān)的調(diào)試信息。 與簡(jiǎn)單地把所有信息轉(zhuǎn)儲(chǔ)到一個(gè)日志文件,然后努力找出哪個(gè)日志記錄來(lái)自哪個(gè)線(xiàn)程(還要擔(dān)心線(xiàn)程爭(zhēng)用日志紀(jì)錄對(duì)象)相比,這種技術(shù)簡(jiǎn)便得多,也有效得多。
ThreadLocal
在基于 servlet 的應(yīng)用程序或工作單元是一個(gè)整體請(qǐng)求的任何多線(xiàn)程應(yīng)用程序服務(wù)器中也是很有用的,因?yàn)樵谔幚碚?qǐng)求的整個(gè)過(guò)程中將要用到單個(gè)線(xiàn)程。您可以通過(guò)前面講述的每線(xiàn)程單子技術(shù)用 ThreadLocal
變量來(lái)存儲(chǔ)各種每請(qǐng)求(per-request)上下文信息。
如果希望線(xiàn)程局部變量初始化其它值,那么需要自己實(shí)現(xiàn)ThreadLocal的子類(lèi)并重寫(xiě)該方法,通常使用一個(gè)內(nèi)部匿名類(lèi)對(duì)ThreadLocal進(jìn)行子類(lèi)化,比如下面的例子,SerialNum類(lèi)為每一個(gè)類(lèi)分配一個(gè)序號(hào):
public class SerialNum { // The next serial number to be assigned private static int nextSerialNum = 0; private static ThreadLocal serialNum = new ThreadLocal() { protected synchronized Object initialValue() { return new Integer(nextSerialNum++); } }; public static int get() { return ((Integer) (serialNum.get())).intValue(); } } |
SerialNum類(lèi)的使用將非常地簡(jiǎn)單,因?yàn)間et()方法是static的,所以在需要獲取當(dāng)前線(xiàn)程的序號(hào)時(shí),簡(jiǎn)單地調(diào)用:
int serial = SerialNum.get(); |
即可。
在線(xiàn)程是活動(dòng)的并且ThreadLocal對(duì)象是可訪(fǎng)問(wèn)的時(shí),該線(xiàn)程就持有一個(gè)到該線(xiàn)程局部變量副本的隱含引用,當(dāng)該線(xiàn)程運(yùn)行結(jié)束后,該線(xiàn)程擁有的所以線(xiàn)程局部變量的副本都將失效,并等待垃圾收集器收集。
ThreadLocal與其它同步機(jī)制的比較
ThreadLocal和其它同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和其它所有的同步機(jī)制都是為了解決多線(xiàn)程中的對(duì)同一變量的訪(fǎng)問(wèn)沖突,在普通的同步機(jī)制中,是通過(guò)對(duì)象加鎖來(lái)實(shí)現(xiàn)多個(gè)線(xiàn)程對(duì)同一變量的安全訪(fǎng)問(wèn)的。這時(shí)該變量是多個(gè)線(xiàn)程共享的,使用這種同步機(jī)制需要很細(xì)致地分析在什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫(xiě),什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放該對(duì)象的鎖等等很多。所有這些都是因?yàn)槎鄠€(gè)線(xiàn)程共享了資源造成的。ThreadLocal就從另一個(gè)角度來(lái)解決多線(xiàn)程的并發(fā)訪(fǎng)問(wèn),ThreadLocal會(huì)為每一個(gè)線(xiàn)程維護(hù)一個(gè)和該線(xiàn)程綁定的變量的副本,從而隔離了多個(gè)線(xiàn)程的數(shù)據(jù),每一個(gè)線(xiàn)程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線(xiàn)程安全的共享對(duì)象,在編寫(xiě)多線(xiàn)程代碼時(shí),可以把不安全的整個(gè)變量封裝進(jìn)ThreadLocal,或者把該對(duì)象的特定于線(xiàn)程的狀態(tài)封裝進(jìn)ThreadLocal。
由于ThreadLocal中可以持有任何類(lèi)型的對(duì)象,所以使用ThreadLocal get當(dāng)前線(xiàn)程的值是需要進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數(shù)的ThreadLocal<T>類(lèi)將從中受益。也可以減少?gòu)?qiáng)制類(lèi)型轉(zhuǎn)換,并將一些錯(cuò)誤檢查提前到了編譯期,將一定程度地簡(jiǎn)化ThreadLocal的使用。
總結(jié)
當(dāng)然ThreadLocal并不能替代同步機(jī)制,兩者面向的問(wèn)題領(lǐng)域不同。同步機(jī)制是為了同步多個(gè)線(xiàn)程對(duì)相同資源的并發(fā)訪(fǎng)問(wèn),是為了多個(gè)線(xiàn)程之間進(jìn)行通信的有效方式;而ThreadLocal是隔離多個(gè)線(xiàn)程的數(shù)據(jù)共享,從根本上就不在多個(gè)線(xiàn)程之間共享資源(變量),這樣當(dāng)然不需要對(duì)多個(gè)線(xiàn)程進(jìn)行同步了。所以,如果你需要進(jìn)行多個(gè)線(xiàn)程之間進(jìn)行通信,則使用同步機(jī)制;如果需要隔離多個(gè)線(xiàn)程之間的共享沖突,可以使用ThreadLocal,這將極大地簡(jiǎn)化你的程序,使程序更加易讀、簡(jiǎn)潔。
實(shí)例:












































結(jié)果:











看樣ThreadLocal工作的很好.請(qǐng)注意ThreadLocal類(lèi)似一個(gè)訪(fǎng)問(wèn)媒介,get()返回的值是存儲(chǔ)在當(dāng)前線(xiàn)程中的.
值得注意的是InheritableThreadLocal類(lèi),可以使ThreadLocal數(shù)據(jù)傳遞到子線(xiàn)程中.
另外servlet線(xiàn)程可能會(huì)被重新利用,要注意在每個(gè)servlet開(kāi)始時(shí)重新初始化.