說說 Java 的執行緒本地化物件容器(ThreadLocal)

JDK1.2+ 就已經提供了 java.lang.ThreadLocal 啦。可能很多小夥伴還不知道它,今天就讓我們來說說它吧O(∩_∩)O~
ThreadLocal 是執行緒的本地化物件容器 。當在多執行緒環境中,使用 ThreadLocal 維護物件時, ThreadLocal 會為每一個使用這個物件的執行緒,分配一個獨立的物件副本 。每一個執行緒可以獨立地使用自己的副本,而不會影響其他執行緒所對應的副本。
InheritableThreadLocal 繼承於 ThreadLocal, 它會為子執行緒複製一份從父執行緒那裡繼承而來的本地變數。所以當必須將本地執行緒變數傳遞給所建立的子執行緒時,應當使用 InheritableThreadLocal。
1 ThreadLocal 定義
方法 | 說明 |
---|---|
public void set(T value) | 設定當前執行緒區域性變數。 |
public T get() | 獲取當前執行緒區域性變數。 |
public void remove() | 刪除當前執行緒區域性變數,這些可以減少記憶體佔用。 另外,當執行緒結束後,對應該執行緒的區域性變數將會被垃圾回收。 |
protected T initialValue() | 獲取該執行緒區域性變數的初始值,它是一個 protected 的方法,所以需要子類覆蓋後才能定製 。 它是一個延遲呼叫方法,只有在第 1 次呼叫 get() 或 set() 時才會執行,並且僅執行 1 次 。該方法預設返回 null。 |
在 ThreadLocal 類中定義了 ThreadLocalMap,用於儲存每一個執行緒的變數副本, ThreadLocalMap 中的鍵為執行緒物件,值為對應執行緒的變數副本 。
2 ThreadLocal 示例
我們利用 ThreadLocal 的特性,來實現一個多線程式列號生成器:
public class SequenceNo { private static ThreadLocal<Integer> no = new ThreadLocal<Integer>() { @Override public Integer initialValue() { return 0; } }; /** * 獲取下一個序列號 * * @return */ public int next() { no.set(no.get() + 1); return no.get(); } public static void main(String[] args) { SequenceNo no = new SequenceNo(); Client c1 = new Client(no); Client c2 = new Client(no); c1.start(); c2.start(); } private static class Client extends Thread { private final SequenceNo no; public Client(SequenceNo no) { this.no = no; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("thread[" + Thread.currentThread().getName() + "] no[" + no .next() + "]"); } } } }
輸出結果:
thread[Thread-0] no[1]thread[Thread-0] no[2]thread[Thread-0] no[3]thread[Thread-0] no[4]thread[Thread-1] no[1]thread[Thread-0] no[5]thread[Thread-1] no[2]thread[Thread-1] no[3]thread[Thread-1] no[4]thread[Thread-1] no[5]
從輸出結果中,我們可以發現每個執行緒所產生的序列號各自產生獨立的序列號,雖然它們都是共享同一個 SequenceNo 例項,這是歸功於 ThreadLocal 為每一個執行緒提供了獨立的副本變數。
3 比較 ThreadLocal 與執行緒同步機制
執行緒同步機制,是通過物件的鎖來保證同一時間內只有一個執行緒可以訪問變數 。 因為變數是多個執行緒共享的,所以這就要求,我們縝密地分析何時讀寫變數、何時鎖定物件、何時釋放物件鎖等繁雜的問題,程式設計難度較大 。
ThreadLocal 為每一個執行緒都提供了獨立的變數副本,從而避免了多執行緒訪問衝突 。 我們在編寫多執行緒程式碼時,可以把不安全的變數封裝進 ThreadLocal,讓它為我們維護這些在多執行緒環境下的變數副本。
ThreadLocal 可以持有任何型別的物件,而且支援泛型,所以使用起來很方便。
對於多執行緒資源共享的問題,執行緒同步機制採用了 “ 以時間換空間 ” 的方式:訪問序列,物件共享 。 而 ThreadLocal 採用了 “ 以空間換時間 ” 的方式:訪問並行,物件獨享 。 執行緒同步機制僅保留一份變數,讓不同的執行緒序列排隊訪問;而 ThreadLocal 則為每一個執行緒都提供了一份獨立的變數副本,因此可以同時訪問而互不干擾。
4 在 Spring 中的應用
通常,只有無狀態的 Bean 才可以在多執行緒環境中共享。但在 Spring 中,絕大部分 Bean 都可以宣告為 singleton 作用域(預設作用域) 。 這是因為 Spring 使用 ThreadLocal 封裝了這些 Bean 中的非執行緒安全的 “ 狀態性物件 ”,因此這些 Bean 就能夠以 singleton 的作用域在多執行緒環境下正常運作啦O(∩_∩)O~