ThreadLocal 實現原理總結
ThreadLocal
用於在不同執行緒中互不干擾的儲存並提供資料。
這裡不對原始碼進行深究,只淺顯的對實現原理進行了解。
本次涉及到的原始碼為 Source for Android 27.
ThreadLocal
的實現,需要藉助到 ThreadLocalMap
。
需要提前交代的:
在一個 Thread
例項內部,都有一個 threadLocals
成員變數(ThreadLocalMap 型別),而這個 threadLocals
內部又維護了一個 Entry
型別的陣列。
而 Entry
是一個 key - value
實體,用於儲存 ThreadLocal - Object
先看一段程式碼:
@Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main3); ThreadLocal<String> threadLocal = new ThreadLocal<>(); new Thread(() -> { threadLocal.set("Thread 1"); Log.d("Test", "Thread 1 => "+threadLocal.get()); }).start(); threadLocal.set("Main"); Log.d("Test", "MainThread => "+threadLocal.get()); }
在這段程式碼裡面,生成了一個 ThreadLocal<String>
例項 threadLocal,然後分別在 UI 執行緒和一個子執行緒裡面去分別進行 set()
和 get()
操作。然後,就會在不同執行緒裡面列印對應的值。
首先看 threadLocal.set()
方法內部:
// ThreadLocal.set() public void set(T value) { Thread t = Thread.currentThread();//獲得呼叫 set() 方法所處的執行緒例項,即 Thread 例項 ThreadLocalMap map = getMap(t);//進一步獲得 Thread 例項的 threadLocals(ThreadLocalMap 型別) 成員變數 if (map != null) map.set(ThreadLocal.this, value); else //如果 t 的 threadLocals == null,則新建一個 //新建的時候會將當前 ThreadLocal 的 this 引用傳遞進去 createMap(t, value); } // ThreadLocal.getMap() ThreadLocalMap getMap(Thread t) { //每個 Thread 例項都會有一個 threadLocals 成員變數 return t.threadLocals; }
在某一執行緒裡面呼叫 threadLocal
的 set(value)
方法,那麼 Thread.currentThread()
就會得到當前執行緒的例項,然後通過該 Thread
例項獲取到其內部的成員變數 threadLocals
(ThreadLocalMap
型別),然後將當前 threadLocal
例項作為 key
值,與形參 value
繫結在一起生成一個 Entry
例項(當然這裡是通常情況,不考慮重複的 key
而替換原值的情況),並存儲到 ThreadLocalMap
內部的 Entry[]
中。
// ThreadLocalMap.set()
private void set(ThreadLocal<?> key, Object value) {
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();
// 如果是重複的 key 值,則替換原有的 value 值
// 這就對應著在同一個執行緒呼叫兩次 threadLocal.set() 情況
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();
}
通過上述內容,可以得到如下關係:
通過 threadLocal.set(value)
就將 threadLocal
例項與 value
繫結在一起存放在了當前執行緒(即 Thread
例項)之中(這是從模糊的概念上來說),或者可以說,當前執行緒根據 threadLocal
例項作為索引,可以儲存對應的 value
值。
只要理解了 threadLocal.set(value)
大致的原理,那麼對於 threadLocal.get()
方法也容易理解了。
// ThreadLocal
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();
}
在 threadLocal.get()
的時候,就會根據當前執行緒( Thread 例項)得到對應的 threadLocals
(ThreadLocalMap
型別),再進一步得到 Entry[]
中 key
值為 threadLocal
的 Entry
例項,最後獲得 Entry
例項的 value
值。
上述,就是 ThreadLocal
的 set(value)
和 get()
的大致實現,雖然有點繞,但是仔細體會一下,還是容易理解的。
彩蛋部分:
另外,我在自己琢磨到似懂非懂的狀態時,就突然產生了一個疑問,為什麼 ThreadLocalMap
內部要維護一個 Entry[]
(陣列),而不是單個的 Entry 例項,因為明明只要儲存 threadLocal-value
的鍵值對。
後來,突然一下就想明白了,看了下面的程式碼,也許就能體會到了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
ThreadLocal<Boolean> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
new Thread(() -> {
threadLocal1.set(true);
threadLocal2.set("Thread 1");
Log.d("Test", "Thread 1 => "+threadLocal1.get());
Log.d("Test", "Thread 1 => "+threadLocal2.get());
}).start();
threadLocal1.set(false);
threadLocal2.set("Main");
Log.d("Test", "MainThread => "+threadLocal1.get());
Log.d("Test", "MainThread => "+threadLocal2.get());
}
對於每一個執行緒(即 Thread
例項)來說,可能遇到需要維護多個不同的 threadLocal-value
的情況(即會產生多個不同的 Entry
例項),因此,就需要一個 Entry[]
來儲存多個 Entry
例項。
最後,還需要說明的一點是,ThreadLocal
的作用是為不同的執行緒儲存對應的物件(實際上是物件的引用,想想 String
型別),如果在兩個執行緒中儲存的都是同一個物件的引用,那麼在兩個執行緒中得到也必然會是同一物件的引用,這點是需要注意的。
public class Test {
int anInt;
public static void main(String[] args) {
Test t = new Test();
t.anInt = 3;
ThreadLocal<Test> local = new ThreadLocal<>();
local.set(t);
local.get().t(10);
new Thread(() -> {
local.set(t);
System.out.println(local.get().anInt);
}).start();
System.out.println(local.get().anInt);
}
public int t(int v) {
anInt = v;
return anInt;
}
}
列印結果:
10
10
可供參考的文章:
ThreadLocal四重奏 系列