引言
無論實際項目實戰還是面試,ThreadLocal
都是一個繞不開的話題,本文主要從源碼角度和大家一起探討下ThreadLocal
的神祕面紗。
ThreadLocal
是什麼?它能幹什麼?ThreadLocal
源碼分析- 總結
一、ThreadLocal
是什麼?它能幹什麼?
ThreadLocal
是一個線程的本地變量, 也就意味着這個變量是線程獨有的,是不能與其他線程共享的,它並不是解決多線程共享變量的問題。
所以ThreadLocal
與線程同步機制不同,線程同步機制是多個線程共享同一個變量,而ThreadLocal
是爲每一個線程創建一個單獨的變量副本,故而每個線程都可以獨立地改變自己所擁有的變量副本,而不會影響其他線程所對應的副本。可以說ThreadLocal
爲多線程環境下變量問題提供了另外一種解決思路。
ThreadLocal
的思想就是用空間換時間,使各線程都能訪問屬於自己這一份的變量副本,變量值不互相干擾,減少同一個線程內的多個函數或者組件之間一些公共變量傳遞的複雜度。
二、ThreadLocal
源碼分析
1、ThreadLocalMap
解析
ThreadLocal
內部定義了一個ThreadLocalMap
的內部類,ThreadLocalMap
實際利用Entry
來實現key-value
的存儲,如下所示:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
ThreadLocalMap
是實現線程隔離機制的關鍵,從以上代碼可以看出Entry
的key
就是ThreadLocal
,而value
就是值。同時,Entry
也繼承WeakReference
,所以說Entry
所對應key
(ThreadLocal
實例)的引用爲一個弱引用。
我們主要來看下核心的getEntry()
、set(ThreadLocal> key, Object value)
方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
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;
// 根據 ThreadLocal 的散列值,查找對應元素在數組中的位置
int i = key.threadLocalHashCode & (len-1);
// 採用“線性探測法”,尋找合適位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 若key 存在,直接覆蓋
if (k == key) {
e.value = value;
return;
}
// key == null,但是存在值(因爲此處的e != null),說明之前的ThreadLocal對象已經被回收了
if (k == null) {
// 用新元素替換陳舊的元素
replaceStaleEntry(key, value, i);
return;
}
}
//創建新元素
tab[i] = new Entry(key, value);
int sz = ++size;
// 如果沒有清理陳舊的 Entry 並且數組中的元素大於了閾值,則進行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
2、核心方法解析
(1) get()
返回此線程局部變量的當前線程副本中的值
public T get() {
//獲取當前線程
Thread t = Thread.currentThread();
//獲取當前線程的成員變量 threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 從當前線程的ThreadLocalMap獲取相對應的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 獲取目標值
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
首先通過當前線程獲取所對應的成員變量ThreadLocalMap
,然後通過ThreadLocalMap
獲取當前ThreadLocal
的Entry
,最後通過所獲取的Entry獲取目標值result
。
(2) set(T value)
將此線程局部變量的當前線程副本中的值設置爲指定值。
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
的set()
方法,key就是當前ThreadLocal
,如果不存在,則調用createMap()
方法新建一個。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
(3) initialValue()
返回此線程局部變量的當前線程的初始值。
protected T initialValue() {
return null;
}
該方法定義爲protected
級別且返回爲null
,需要其子類實現其功能,所以我們在使用ThreadLocal
的時候一般都應該覆蓋該方法。該方法不能顯示調用,只有在第一次調用get()
或者set()
方法時纔會被執行,並且僅執行1次。
(4) remove()
移除此線程局部變量當前線程的值。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
三、總結
1、多個線程去獲取一個共享變量時,要求獲取的是這個變量的初始值的副本。每個線程存儲這個變量的副本,對這個變量副本的改變不會影響變量本身。適用於多個線程依賴不同變量值完成操作的場景。比如:
- 多數據源的切換
- spring聲明式事務
2、將ThreadLocal
設置成private static
的,這樣ThreadLocal
會盡量和線程本身一起回收。