1. 程式人生 > >Java原始碼解析ThreadLocal及使用場景

Java原始碼解析ThreadLocal及使用場景

ThreadLocal是在多執行緒環境下經常使用的一個類。

這個類並不是為了解決多執行緒間共享變數的問題。舉個例子,在一個電商系統中,用一個Long型變量表示某個商品的庫存量,多個執行緒需要訪問庫存量進行銷售,並減去銷售數量,以更新庫存量。在這個場景中,是不能使用ThreadLocal類的。

ThreadLocal適用的場景是,多個執行緒都需要使用一個變數,但這個變數的值不需要在各個執行緒間共享,各個執行緒都只使用自己的這個變數的值。這樣的場景下,可以使用ThreadLocal。此外,我們使用ThreadLocal還能解決一個引數過多的問題。例如一個執行緒內的某個方法f1有10個引數,而f1呼叫f2時,f2又有10個引數,這麼多的引數傳遞十分繁瑣。那麼,我們可以使用ThreadLocal來減少引數的傳遞,用ThreadLocal定義全域性變數,各個執行緒需要引數時,去全域性變數去取就可以了。

接下來我們看一下ThreadLocal的原始碼。首先是類的介紹。如下圖。這個類提供了執行緒本地變數。這些變數使每個執行緒都有自己的一份拷貝。ThreadLocal期望能夠管理一個執行緒的狀態,例如使用者id或事務id。例如下面的例子產生執行緒本地的唯一id。執行緒的id是第一次呼叫時進行復制,並且在後面的呼叫中保持不變。

This class provides thread-local variables. 
These variables differ from their normal counterparts in that each thread that accesses
 one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that
 wish to associate state with a thread (e.g., a user ID or Transaction ID).
For example, the class below generates unique identifiers local to each thread.
 A thread's id is assigned the first time it invokes ThreadId.get() and 
remains unchanged on subsequent calls.
   import java.util.concurrent.atomic.AtomicInteger;
  
   public class ThreadId {
       // Atomic integer containing the next thread ID to be assigned
       private static final AtomicInteger nextId = new AtomicInteger(0);
  
       // Thread local variable containing each thread's ID
       private static final ThreadLocal<Integer> threadId =
           new ThreadLocal<Integer>() {
               @Override protected Integer initialValue() {
                   return nextId.getAndIncrement();
           }
       };
  
       // Returns the current thread's unique ID, assigning it if necessary
       public static int get() {
           return threadId.get();
       }
   }
   
Each thread holds an implicit reference to its copy of a thread-local 
variable as long as the thread is alive and the ThreadLocal instance is 
accessible; after a thread goes away, all of its copies of thread-local
 instances are subject to garbage collection (unless other references to 
these copies exist).

下面看一下set方法。如下圖。set方法的作用是,把執行緒本地變數的當前執行緒的拷貝設定為指定的值。大部分子類無需重寫該方法。首先獲取當前執行緒,然後獲取當前執行緒的ThreadLocalMap。如果ThreadLocalMap不為null,則設定當前執行緒的值為指定的值,否則呼叫createMap方法。

獲取執行緒的ThreadLocalMap物件,是直接返回的執行緒的threadLocals,型別為ThreadLocalMap。也就是說,每個執行緒都有一個ThreadLocalMap物件,用於儲存該執行緒關聯的所有的ThreadLocal型別的變數。ThreadLocalMap的key是ThreadLocal,value是該ThreadLocal對應的值。具體什麼意思呢?在程式中,我們可以定義不止一個ThreadLocal物件,一般會有多個,比如定義3個ThreadLocal<String>,再定義2個ThreadLocal<Integer>,而每個執行緒可能都需要訪問全部這些ThreadLocal的變數,那麼,我們用什麼資料結構來實現呢?當然,最好的方式就是像原始碼中的這樣,每個執行緒有一個ThreadLocalMap,key為ThreadLocal變數名,而value為該執行緒在該ThreadLocal變數的值。這個設計實在是太巧妙了。

寫到這裡,自己回想起之前換工作面試時,面試官問自己關於ThreadLocal的實現原理。那個時候,為了準備面試,自己只在網上看了一些面試題,並沒有真正掌握,在回答這個問題時,我有印象,自己回答的是用一個map,執行緒的id值作為key,變數值作為value,誒,露餡了啊。

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

接下來看一下get方法。原始碼如下。首先獲取當前執行緒的ThreadLocalMap,然後,從ThreadLocalMap獲取該ThreadLocal變數對應的value,然後返回value。如果ThreadLocalMap為null,則說明該執行緒還沒有設定該ThreadLocal變數的值,那麼就返回setInitialValue方法的返回值。其中的initialValue方法的返回值,通常情況下為null。但是,子類可以重寫initialValue方法以返回期望的值。

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    protected T initialValue() {
        return null;
    }

文章的最後,簡單介紹一下ThreadLocalMap這個類,該類是ThreadLocal的靜態內部類。它對HashMap進行了改造,用於儲存各個ThreadLocal變數和某執行緒的該變數的值的對映關係。每個執行緒都有一個ThreadLocalMap型別的屬性。ThreadLocalMap中的table陣列的長度,與該執行緒訪問的ThreadLocal型別變數的個數有關,而與別的無關。

This is the end。