1. 程式人生 > >說說 Java 的執行緒本地化物件容器(ThreadLocal)

說說 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~