[java] ThreadLocal類解析
概述
該類的作用是提供執行緒區域性(thread-local)的變數, 這些變數與正常變數的不同之處在於(通過其get或set方法)訪問一個執行緒的每個執行緒都有自己的獨立初始化的變數副本。ThreadLocal例項通常是希望儲存狀態與執行緒(例如,使用者ID或事務ID)關聯的私有靜態欄位。
例如,下面的類生成每個執行緒本地的唯一識別符號。第一次呼叫ThreadId.get方法時,分配一個執行緒的ID,在後續呼叫中保持不變。
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(); } }
每個執行緒都保持對執行緒本地變數的副本的隱式引用,只要執行緒是活的並且ThreadLocal例項是可訪問的;線上程離開後,執行緒本地例項的所有副本都由垃圾收集的處理(除非對這些副本有其他引用)。
Set方法
程式碼實現如下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
首先根據呼叫這個方法的物件獲取當前執行緒,然後獲取屬於這個執行緒的ThreadLocalMap例項。 ThreadLocalMap是ThreadLocal的一個內部類,這個類的程式碼佔據了ThreadLocal類的一半還多。可見其重要性。Threadlocal的方法都是基於這個類實現的。
從上面程式碼我們可以看到,如果map不為空就呼叫set方法
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
Key即為傳入的引數this,即呼叫這個set方法的ThreadLocal例項,我們知道每個執行緒都會有自己對應的ThreadLocal例項,而這裡把這個例項拿來作為了map的key,因此,這就可以解釋清楚了為什麼ThreadLocal是執行緒之間隔離的。
實現的邏輯比較簡單:
根據傳入的key,藉助了Unsafe類去計算它的hashcode值,然後value不用做任何其他處理。最後是儲存在了一個Entry型別的數組裡面。
Entry則是ThreadLocalMap這個類的有一個內部類,定義如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到,這個類裡面有一個Object型別的成員變數value,這個成員變數就是我們拿來儲存值的變數。
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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
有一個很有意思的問題,這個方法不是get方法嗎?那它為什麼沒有引數,沒有key呢?其實在分析set方法的時候我們已經回答了這個問題了,ThreadLocal的map的key是ThreadLocal例項本身。再看程式碼:首先仍舊是獲取了當前執行緒,再去拿了它的ThreadLocalMap,這個時候,直接去呼叫了這個map的getEntry方法,然後傳入的引數是this ,即這個ThreadLocal物件本身。非常巧妙的邏輯。
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
呼叫了ThreadLocalMap的remove方法,引數是ThreadLocal例項,也就是map的key,即這裡的this。實現如下
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
遍歷table,找到這個key對於的Entry,然後把它的值設為null。
ThreadLocal Map定義
在ThreadLocal類中,物件是儲存在一個內部類ThreadLocalMap的成員變數table裡面的,定義如下:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
...
}
可以看到table是一個Entry型別的陣列,Entry也是一個類,這個類裡面又有一個成員變數Object型別的value,這就是ThreadLocal這個類儲存值得地方。
典型使用場景
比較出名的一個應用場景是 Web伺服器端的Session的儲存。
Web容器採用執行緒隔離的多執行緒模型,也就是每一個請求都會對應一條執行緒,執行緒之間相互隔離,沒有共享資料。這樣能夠簡化程式設計模型,程式設計師可以用單執行緒的思維開發這種多執行緒應用。
當收到一個請求時,可以將當前Session資訊儲存在ThreadLocal中,在請求處理過程中可以隨時使用Session資訊,每個請求之間的Session資訊互不影響。當請求處理完成後通過remove方法將當前Session資訊清除即可。