前言
說起 ThreadLocal,大家可能會比較陌生,但是如果想要比較好地理解 Android 的消息機制,ThreadLocal 是必須要掌握的,這是因為 Looper 的工作原理,就跟 ThreadLocal 有很大的關系,理解 ThreadLocal 的實現方式有助于我們理解 Looper 的工作原理,這篇文章就從 ThreadLocal 的用法講起,一步一步帶大家理解 ThreadLocal。
一、ThreadLocal 是什么
ThreadLocal 是一個線程內部的數據存儲類,通過它可以在 指定的線程中 存儲數據,數據存儲以后,只有在指定線程中可以獲取到存儲的數據,對于其他線程來說則無法獲取到數據。
一般來說,當某些數據是以線程為作用域并且不同線程具有不同的數據副本的時候,就可以考慮采用 ThreadLocal。
二、基本用法
創建,支持泛型
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
set 方法
mStringThreadLocal.set("developerHaoz");
get 方法
mStringThreadLocal.get();
接下來用一個完整的例子,幫助大家理解 ThreadLocal
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBooleanThreadLocal.set(true);
Log.d(TAG, "Current Thread: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());
new Thread("Thread#1"){
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG, "Thread 1: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
Log.d(TAG, "Thread 2: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());
}
}.start();
}
在上面的代碼中,在主線程中設置 mBooleanThrealLocal 的值為 true,在子線程 1 中設置為 false,在子線程 2 中不設置 mBooleanThrealLocal 的值,然后分別在 3 個線程中通過 get() 方法獲取 mBooleanThrealLocal 的值
從上面的日志中可以看出,雖然在不同的線程中訪問的是同一個 ThrealLocal 對象,但是它們通過 ThrealLocal 獲取到的值確實不一樣的,這就是 ThrealLocal 的奇妙之處了。
ThrealLocal 之所以有這么奇妙的效果,就是因為不同線程訪問同一個 ThrealLocal 的 get() 方法,ThrealLocal 內部都會從各自的線程中取出一個數組,然后再從數組中根據當前 ThrealLocal 的索引去查找不同的 value 值。
三、ThrealLocal 工作原理
ThrealLocal 是一個泛型類,它的定義為 public class ThrealLocal<T>,只要弄清楚 ThrealLocal 的 set() 和 get() 方法就可以明白它的工作原理了。
1、ThrealLocal 的 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);
}
可以看到在 set() 方法中,先獲取到當前線程,然后通過 getMap(Thread t) 方法獲取一個 ThreadLocalMap,如果這個 map 不為空的話,就將 ThrealLocal 和 我們想存放的 value 設置進去,不然的話就創建一個 ThrealLocalMap 然后再進行設置。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap 其實是 ThreadLocal 里面的靜態內部類,而每一個 Thread 都有一個對應的 ThrealLocalMap,因此獲取當前線程的 ThrealLocal 數據就變得異常簡單了。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
下面看一下,ThrealLocal 的值到底是如何在 threadLocals 中進行存儲的。在 threadLocals 內部有一個數組,private Entry[] table,ThrealLocal 的值就存在這個 table 數組中。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}
2、ThrealLocal 的 get() 方法
上面分析了 ThreadLocal 的 set() 方法,這里分析它的 get() 方法,代碼如下
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
可以發現,ThrealLocal 的 get() 方法的邏輯也比較清晰,它同樣是取出當前線程的 threadLocals 對象,如果這個對象為 null,就調用 setInitialValue() 方法
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在 setInitialValue() 方法中,將 initialValue() 的值賦給我們想要的值,默認情況下,initialValue() 的值為 null,當然也可以重寫這個方法。
protected T initialValue() {
return null;
}
如果 threadLocals 對象不為 null 的話,那就取出它的 table 數組并找出 ThreadLocal 的 reference 對象在 table 數組中的位置。
從 ThreadLocal 的 set() 和 get() 方法可以看出,他們所操作的對象都是當前線程的 threalLocals 對象的 table 數組,因此在不同的線程中訪問同一個 ThreadLocal 的 set() 和 get() 方法,他們對 ThreadLocal 所做的 讀 / 寫
操作權限僅限于各自線程的內部,這就是為什么可以在多個線程中互不干擾地存儲和修改數據。
總結
ThreadLocal 是線程內部的數據存儲類,每個線程中都會保存一個 ThreadLocal.ThreadLocalMap threadLocals = null;
,ThreadLocalMap 是 ThreadLocal 的靜態內部類,里面保存了一個 private Entry[] table
數組,這個數組就是用來保存 ThreadLocal 中的值。通過這種方式,就能讓我們在多個線程中互不干擾地存儲和修改數據。