這個實現的性能不會很好,因為每個 get() 和 set() 操作都需要 values 映射表上的同步,而且如果多個線程同時訪問同一個 ThreadLocal ,那么將發生爭用。此外,這個實現也是不切實際的,因為用 Thread 對象做 values 映射表中的關鍵字將導致無法在線程退出后對 Thread 進行垃圾回收,而且也無法對死線程的 ThreadLocal 的特定于線程的值進行垃圾回收。
get()
set()
values
ThreadLocal
Thread
用 ThreadLocal 實現每線程 Singleton
線程局部變量常被用來描繪有狀態“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個變量封裝進 ThreadLocal ,或者是通過把對象的特定于線程的狀態封裝進 ThreadLocal 。例如,在與數據庫有緊密聯系的應用程序中,程序的很多方法可能都需要訪問數據庫。在系統的每個方法中都包含一個 Connection 作為參數是不方便的 — 用“單子”來訪問連接可能是一個雖然更粗糙,但卻方便得多的技術。然而,多個線程不能安全地共享一個 JDBC Connection 。如清單 3 所示,通過使用“單子”中的 ThreadLocal ,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個引用。這樣,我們可以認為 ThreadLocal 允許我們創建 每線程單子。
Connection
任何創建的花費比使用的花費相對昂貴些的有狀態或非線程安全的對象,例如 JDBC Connection 或正則表達式匹配器,都是可以使用每線程單子(singleton)技術的好地方。當然,在類似這樣的地方,您可以使用其它技術,例如用池,來安全地管理共享訪問。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因為池實現必須使用同步,以維護池數據結構的完整性,如果所有線程使用同一個池,那么在有很多線程頻繁地對池進行訪問的系統中,程序性能將因爭用而降低。
用 ThreadLocal 簡化調試日志紀錄
其它適合使用 ThreadLocal 但用池卻不能成為很好的替代技術的應用程序包括存儲或累積每線程上下文信息以備稍后檢索之用這樣的應用程序。例如,假設您想創建一個用于管理多線程應用程序調試信息的工具。您可以用如清單 4 所示的 DebugLogger 類作為線程局部容器來累積調試信息。在一個工作單元的開頭,您清空容器,而當一個錯誤出現時,您查詢該容器以檢索這個工作單元迄今為止生成的所有調試信息。
DebugLogger
在您的代碼中,您可以調用 DebugLogger.put() 來保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發生了一個錯誤),您能夠容易地檢索與某個特定線程相關的調試信息。 與簡單地把所有信息轉儲到一個日志文件,然后努力找出哪個日志記錄來自哪個線程(還要擔心線程爭用日志紀錄對象)相比,這種技術簡便得多,也有效得多。
DebugLogger.put()
ThreadLocal 在基于 servlet 的應用程序或工作單元是一個整體請求的任何多線程應用程序服務器中也是很有用的,因為在處理請求的整個過程中將要用到單個線程。您可以通過前面講述的每線程單子技術用 ThreadLocal 變量來存儲各種每請求(per-request)上下文信息。