源碼分析之ThreadLocal

源碼分析之ThreadLocal

概念描述

ThreadLocal的作用是提供了線程內的局部變量。當使用ThreadLocal變量在多線程環境下,每個線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
JDK API 描述:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code 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).
*/

嘗試翻譯下:

ThreadLocal提供一個線程局部變量。這些變量區別于線程內其他普通變量的地方是:訪問變量的每個線程(通過getset方法)都有自身獨有的變量初始化副本。實例通常是用private static修飾為私有靜態對象。希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。

ThreadLocal VS synchronized

ThreadLocal并不是一個線程,而是一個線程局部變量。功能就是為每個使用變量的線程提供一個變量副本,每個線程可以獨立修改變量副本,不與其他線程相互沖突,保證線程隔離。

synchronized關鍵字利用JVM鎖機制保證臨界區或者變量訪問的原子性。同步機制中保證同一時間只有一個線程可以訪問變量,從而實現資源共享。

同步機制采用的是以時間換空間方式,提供一個變量,多線程環境下各個線程通過阻塞排隊訪問。ThreadLocal則以空間換時間方式,多線程環境,每個線程訪問自己獨有的變量副本,同時訪問,相互隔離。

ThreadLocal源碼分析

ThreadLocal類結構大致如下:

ThreadLocal-w400
ThreadLocal-w400

主要看看經常使用的其中的get()set()remove()方法。

靜態內部類ThreadLocalMap

在看具體方法的前,先看看ThreadLocal中的靜態內部類ThreadLocalMap,這個Map為每個線程復制一個變量副本存儲其中。類接口如下:

ThreadLocalMap-w400
ThreadLocalMap-w400

這里的EntryWeakReference的子類,Entrykey是弱引用,指向ThreadLocal。當ThreadLocal實例被設置成null時,ThreadLocal就會GC回收。這樣就會存在keynullentry。而此時value。這些keynullentryvalue就會一直存在一條從當前線程連接過來的強引用:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
只有線程結束的時候,value才會被回收。在使用線程池的時候,線程放回線程池一直不被使用或者使用沒有調用ThreadLocalget()、set()、remove(),就會造成內存泄露。所以為了避免內存泄漏,每次使用完 ThreadLocal ,都調用它的remove()方法,清除數據。

從源碼可以看出線程是用Entry來保存ThreadLocal提供的變量副本。而Entrykey是將其Hashcode與數組長度-1進行與操作:key.threadLocalHashCode & (table.length - 1)。 還可以看到有個HASH_INCREMENT,它是一個常量:0x61c88647ThreadLocalMap的長度被要求為2的N次方,選定這個值,是因為可以讓hash的結果在2的N次方內盡可能均勻分布,減少沖突的概率。
ThreadLocalMap解決沖突的方法是開放地址法,所謂的開放地址法是指,發生Hash沖突后,按照某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置為止。

get()方法

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); // 獲取線程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

調用ThreadLocal.get()方法獲取變量時,首先獲取當前線程引用,以此為key去獲取相應的ThreadLocalMap,如果Map不存在則初始化一個,否則返回其中的變量。

Thread類中:

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

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

每個線程都包含了兩個ThreadLocalMap對象的引用。每個線程訪問ThreadLocal變量都是訪問其存在ThreadLocalMap自身為keyvalue值。

set()方法

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

獲取當前線程的引用,首先獲取當前線程引用,以此為key去獲取相應的ThreadLocalMap。如果map存在,更新value,否則創建并存儲該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
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

remove()方法比較簡單,獲取當前線程的ThreadLocalMap,然后移除map中的局部變量在當前線程的值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容