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來解決線程安全的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章