1. 程式人生 > >ThreadLocal示例及原始碼淺析

ThreadLocal示例及原始碼淺析

實現資料隔離

瞭解一個東西,我們當然要先問為什麼要了解他。

在多執行緒的訪問環境下,我們都會考慮執行緒安全的問題,所謂執行緒安全,就是為了確保多個執行緒訪問的資源能與在單執行緒訪問環境下返回的保持結果一致,不會產生二義性,也可以說是保證多執行緒安全訪問競爭資源的一種手段。

synchronized關鍵字和Java5新增的java.util.concurrent.locks下的Lock和ReentrantLock包從處理機制上來說,是同一類辦法。即通過對物件或者程式碼塊加鎖的方式,保證每個執行緒在訪問上鎖資源時都必須先獲得物件的鎖,然後才能對資源進行訪問,其他執行緒在未獲得物件資源的鎖之前,只能阻塞等待。

而ThreadLocal則另闢蹊徑,它不強制性地讓資源同時只能被一個執行緒訪問,而是允許多個執行緒併發訪問資源,為了保證共享資源的資料隔離性和一致性,通過為每個執行緒繫結一個共享資源的資料副本,讓公有資源複製分發到每個執行緒實現私有化,每個執行緒用一個數據副本大家各用各的,互不相干,從而實現執行緒安全。

示例

程式碼

package threadLocal;

public class Test {
    //定義一個儲存執行緒id的threadLocal副本
    ThreadLocal<Long> threadId = new ThreadLocal<Long>();
//定義一個儲存執行緒name的threadLocal副本 ThreadLocal<String> threadName = new ThreadLocal<String>(); public static void main(String[] args) throws InterruptedException { final Test test = new Test(); //main執行緒 test.threadId.set(Thread.currentThread().getId()); test.threadName
.set(Thread.currentThread().getName()); System.out.println("main執行緒的id:" + test.threadId.get()); System.out.println("main執行緒的Name:" + test.threadName.get()); //一個新的執行緒 Thread anotherThread = new Thread(){ public void run(){ test.threadId.set(Thread.currentThread().getId()); test.threadName.set(Thread.currentThread().getName()); System.out.println("another執行緒的id:"+ test.threadId.get()); System.out.println("another執行緒的Name:" + test.threadName.get()); } }; anotherThread.start(); //主執行緒main呼叫another執行緒的join()方法,就要等待another執行緒執行完畢,主執行緒才會繼續往下執行 anotherThread.join(); System.out.println("main執行緒的id:" + test.threadId.get()); System.out.println("main執行緒的Name:" + test.threadName.get()); } }

執行結果
這裡寫圖片描述

我們在類中聲明瞭兩個ThreadLocal物件,一個物件用來存放當前執行執行緒的id,另一個物件用來存放當前執行執行緒的Name。執行後可以看出,兩個執行緒,主執行緒main和我們新建的執行緒anotherThread執行緒操作的雖然是用一個test物件下的ThreadLocal物件,但是他們各自的屬性資料都是隔離的,分別記錄了自己的值,取出後也都保持了資料的隔離性。

總的來說,每個執行緒都會在內部維護的這個ThreadLocalMap可以看做一個Map,每個ThreadLocal都是這個Map中鍵值對的Key,通過Key值就可以對資料進行操作,而Value就是我們針對每個ThreadLocal物件set的那個值,正如上例中的:

test.threadId.set(Thread.currentThread().getId());
test.threadName.set(Thread.currentThread().getName());

為什麼我會做出這樣的類比,下面就帶大家看一看ThreadLocal的原始碼。

ThreadLocal原始碼

看了ThreadLocal的一個簡單應用,我們接下來可以看看ThreadLocal具體的實現過程。

public class ThreadLocal<T>

從ThreadLocal類的定義可以看到ThreadLocal的泛型宣告,因此理論上ThreadLocal中是可以存放任何型別的資料資源的。

這裡寫圖片描述

上圖中紅框內的三個方法是ThreadLocal的核心方法,很好記,無非是儲存資料,取出資料和移除資料。

get()

/**
     * 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)
                return (T)e.value;
        }
        return setInitialValue();
    }

當我們在呼叫get()方法的時候,先獲取當前執行緒,然後獲取到當前執行緒的ThreadLocalMap物件,如果非空,那麼取出ThreadLocal的value,否則進行初始化,初始化就是將initialValue的值set到ThreadLocal中。

/**
     * 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;
    }

set()

/**
     * 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);
    }

而set則是常規設定,獲取當前執行緒,然後獲取到當前執行緒的ThreadLocalMap物件,如果非空,則設定value值,否則就新建一個ThreadLocalMap用於存放value。

remove()

/**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * <tt>initialValue</tt> method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

Remove就更加簡單了,同樣是獲取當前執行緒,然後獲取到當前執行緒的ThreadLocalMap物件,如果非空,則移除ThreadLocalMap中存放的值。

至此可以看出這些方法都是圍繞著ThreadLocalMap 在操作,那麼ThreadLocalMap 是一個什麼東西。

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

每個Thread物件內部都維護了一個ThreadLocalMap這樣一個ThreadLocal的Map,可以存放若干個ThreadLocal。我們在實際操作中就是針對每個執行緒中宣告的若干個ThreadLocal物件進行資料副本的操作。之前說過,每個ThreadLocal都是這個Map中鍵值對的Key,通過key值就可以對資料進行操作,這一點就體現在ThreadLocalMap 的設定上。

總結

在實際應用中,當很多執行緒需要多次使用同一個物件,並且需要該物件具有相同初始化值的時候最適合使用ThreadLocal。比如jdbc的Connection屬性,這個連線屬性是每個資料訪問執行緒都需要使用到的,並且各自使用各自的,所以需要通過資料副本的形式來保證執行緒間訪問互不干擾。

相較於加鎖機制實現執行緒安全,ThreadLocal是非阻塞的,當在效能上有特殊的要求是,我們可以優先考慮採用ThreadLocal來解決執行緒安全的問題。