源碼分析之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
提供一個線程局部變量。這些變量區別于線程內其他普通變量的地方是:訪問變量的每個線程(通過get
或set
方法)都有自身獨有的變量初始化副本。實例通常是用private static
修飾為私有靜態對象。希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。
ThreadLocal VS synchronized
ThreadLocal
并不是一個線程,而是一個線程局部變量。功能就是為每個使用變量的線程提供一個變量副本,每個線程可以獨立修改變量副本,不與其他線程相互沖突,保證線程隔離。
synchronized
關鍵字利用JVM
鎖機制保證臨界區或者變量訪問的原子性。同步機制中保證同一時間只有一個線程可以訪問變量,從而實現資源共享。
同步機制采用的是以時間換空間
方式,提供一個變量,多線程環境下各個線程通過阻塞排隊訪問。ThreadLocal
則以空間換時間
方式,多線程環境,每個線程訪問自己獨有的變量副本,同時訪問,相互隔離。
ThreadLocal源碼分析
ThreadLocal
類結構大致如下:

主要看看經常使用的其中的get()
、set()
、remove()
方法。
靜態內部類ThreadLocalMap
在看具體方法的前,先看看ThreadLocal
中的靜態內部類ThreadLocalMap
,這個Map為每個線程復制一個變量副本存儲其中。類接口如下:

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

從源碼可以看出線程是用Entry
來保存ThreadLocal
提供的變量副本。而Entry
的key
是將其Hashcode
與數組長度-1進行與操作:key.threadLocalHashCode & (table.length - 1)。
還可以看到有個HASH_INCREMENT
,它是一個常量:0x61c88647
。ThreadLocalMap
的長度被要求為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
自身為key
的value
值。
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
中的局部變量在當前線程的值。