前言
說(shuō)起 ThreadLocal抛猖,大家可能會(huì)比較陌生政钟,但是如果想要比較好地理解 Android 的消息機(jī)制,ThreadLocal 是必須要掌握的樟结,這是因?yàn)?Looper 的工作原理养交,就跟 ThreadLocal 有很大的關(guān)系,理解 ThreadLocal 的實(shí)現(xiàn)方式有助于我們理解 Looper 的工作原理瓢宦,這篇文章就從 ThreadLocal 的用法講起碎连,一步一步帶大家理解 ThreadLocal。
一驮履、ThreadLocal 是什么
ThreadLocal 是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi)鱼辙,通過(guò)它可以在 指定的線程中 存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后玫镐,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)倒戏,對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。
一般來(lái)說(shuō)恐似,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時(shí)候杜跷,就可以考慮采用 ThreadLocal。
二矫夷、基本用法
創(chuàng)建葛闷,支持泛型
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
set 方法
mStringThreadLocal.set("developerHaoz");
get 方法
mStringThreadLocal.get();
接下來(lái)用一個(gè)完整的例子,幫助大家理解 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();
}
在上面的代碼中双藕,在主線程中設(shè)置 mBooleanThrealLocal 的值為 true淑趾,在子線程 1 中設(shè)置為 false,在子線程 2 中不設(shè)置 mBooleanThrealLocal 的值忧陪,然后分別在 3 個(gè)線程中通過(guò) get() 方法獲取 mBooleanThrealLocal 的值
從上面的日志中可以看出扣泊,雖然在不同的線程中訪問(wèn)的是同一個(gè) ThrealLocal 對(duì)象,但是它們通過(guò) ThrealLocal 獲取到的值確實(shí)不一樣的嘶摊,這就是 ThrealLocal 的奇妙之處了延蟹。
ThrealLocal 之所以有這么奇妙的效果,就是因?yàn)椴煌€程訪問(wèn)同一個(gè) ThrealLocal 的 get() 方法更卒,ThrealLocal 內(nèi)部都會(huì)從各自的線程中取出一個(gè)數(shù)組等孵,然后再?gòu)臄?shù)組中根據(jù)當(dāng)前 ThrealLocal 的索引去查找不同的 value 值稚照。
三蹂空、ThrealLocal 工作原理
ThrealLocal 是一個(gè)泛型類(lèi)俯萌,它的定義為 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() 方法中,先獲取到當(dāng)前線程辨萍,然后通過(guò) getMap(Thread t) 方法獲取一個(gè) ThreadLocalMap棋恼,如果這個(gè) map 不為空的話,就將 ThrealLocal 和 我們想存放的 value 設(shè)置進(jìn)去锈玉,不然的話就創(chuàng)建一個(gè) ThrealLocalMap 然后再進(jìn)行設(shè)置爪飘。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap 其實(shí)是 ThreadLocal 里面的靜態(tài)內(nèi)部類(lèi),而每一個(gè) Thread 都有一個(gè)對(duì)應(yīng)的 ThrealLocalMap拉背,因此獲取當(dāng)前線程的 ThrealLocal 數(shù)據(jù)就變得異常簡(jiǎn)單了师崎。
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 中進(jìn)行存儲(chǔ)的椅棺。在 threadLocals 內(nèi)部有一個(gè)數(shù)組犁罩,private Entry[] table,ThrealLocal 的值就存在這個(gè) table 數(shù)組中两疚。
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();
}
可以發(fā)現(xiàn)丐巫,ThrealLocal 的 get() 方法的邏輯也比較清晰,它同樣是取出當(dāng)前線程的 threadLocals 對(duì)象勺美,如果這個(gè)對(duì)象為 null鞋吉,就調(diào)用 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() 的值賦給我們想要的值励烦,默認(rèn)情況下谓着,initialValue() 的值為 null,當(dāng)然也可以重寫(xiě)這個(gè)方法坛掠。
protected T initialValue() {
return null;
}
如果 threadLocals 對(duì)象不為 null 的話赊锚,那就取出它的 table 數(shù)組并找出 ThreadLocal 的 reference 對(duì)象在 table 數(shù)組中的位置。
從 ThreadLocal 的 set() 和 get() 方法可以看出屉栓,他們所操作的對(duì)象都是當(dāng)前線程的 threalLocals 對(duì)象的 table 數(shù)組舷蒲,因此在不同的線程中訪問(wèn)同一個(gè) ThreadLocal 的 set() 和 get() 方法,他們對(duì) ThreadLocal 所做的 讀 / 寫(xiě)
操作權(quán)限僅限于各自線程的內(nèi)部友多,這就是為什么可以在多個(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)牲平。
總結(jié)
ThreadLocal 是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),每個(gè)線程中都會(huì)保存一個(gè) ThreadLocal.ThreadLocalMap threadLocals = null;
域滥,ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類(lèi)纵柿,里面保存了一個(gè) private Entry[] table
數(shù)組蜈抓,這個(gè)數(shù)組就是用來(lái)保存 ThreadLocal 中的值。通過(guò)這種方式昂儒,就能讓我們?cè)诙鄠€(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)沟使。