ThreadLocal的作用
ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù)浆兰,數(shù)據(jù)存儲以后磕仅,只有在指定的線程中可以獲取到存儲的數(shù)據(jù),對于其他線程來說則無法取到數(shù)據(jù)簸呈。
ThreadLocal的主要作用
輕松實現(xiàn)一些看起來很復雜的功能榕订,適合以下一些應用場景。
- 應用場景1
某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)的副本時蜕便,就可以考慮用ThreadLocal
劫恒。
例如:Android中,Handler需要獲取當前線程的Looper轿腺,很顯然Looper的作用域是線程并且不同線程具有不同的Looper两嘴。
通過ThreadLocal
就可以輕松實現(xiàn)Looper在線程中的存取。
- 應用場景2
復雜邏輯下的對象傳遞吃溅,比如監(jiān)聽器的傳遞溶诞,有些時候一個線程中的任務過于復雜,我們又需要監(jiān)聽器能夠貫穿整個線程的執(zhí)行過程决侈。
采用ThreadLocal
可以讓監(jiān)聽器作為線程內(nèi)的全局對象而存在,在線程內(nèi)部只要通過get方法就可以獲取到監(jiān)聽器。
ThreadLocal的使用示例
ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
mBooleanThreadLocal.set(true);
Log.d("@@@", "[Thread@main]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
new Thread("Thread@1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d("@@@", "[Thread@1]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread@2") {
@Override
public void run() {
Log.d("@@@", "[Thread@2]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
}
}.start();
輸出的日志如下
D/@@@: [Thread@main]mBooleanThreadLocal = true
D/@@@: [Thread@1]mBooleanThreadLocal = false
D/@@@: [Thread@2]mBooleanThreadLocal = null
在上面示例代碼中赖歌,主線程的mBooleanThreadLocal的值設置為true枉圃,子線程1的mBooleanThreadLocal的值設置為false,子線程2的mBooleanThreadLocal的值不設置庐冯。
從日志可以看出孽亲,雖然在不同線程中訪問同一個ThreadLocal對象,但是他們通過ThreadLocal獲取到值卻是不一樣的展父,這就是ThreadLocal的神奇之處返劲。
ThreadLocal的實現(xiàn)原理
ThreadLocal是一個泛型類,定義為public class ThreadLocal<T>栖茉,只要弄清楚ThreadLocal的get方法和set方法篮绿,就可以明白它的實現(xiàn)原理。
ThreadLocal的set方法(Android API 26)吕漂,源碼如下
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方法來獲取當前線程的ThreadLocalMap,這個Map是一個自定義的hash map惶凝,key是TheadLocal吼虎,value是對應存儲的值。
ThreadLocal的get方法(Android API 26)苍鲜,源碼如下
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源碼可以看出思灰,首先也是一樣用getMap方法來獲取當前線程的ThreadLocalMap,然后根據(jù)key=當前ThreadLocal來獲取對應的value值混滔。
從ThreadLocal的set和get方法可以看出洒疚,它們所操作的都是當前線程的ThreadLocalMap對象。
因此在不同線程中遍坟,訪問同一個ThreadLocal的set和get方法拳亿,它們對ThreadLocalde的讀、寫操作僅限于各自線程的內(nèi)部愿伴,從而使ThreadLocal可以在多個線程中互不干擾地存儲和修改數(shù)據(jù)肺魁。
ThreadLocal在Android中的應用
獲取當前線程的Looper
在Looper類中,定義了一個ThreadLocal靜態(tài)常量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
1隔节、初始化Looper鹅经,即調(diào)用Looper.perpar(),源碼如下
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
從源碼可以看到怎诫,在一個線程中初始化Looper瘾晃,是用ThreadLocal存儲Looper對象。同時保證一個線程只擁有一個Looper幻妓,否則在初始化會報錯
Only one Looper may be created per thread
2蹦误、獲取當前線程的Looper,即Looper.myLooper(),源碼如下
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
從源碼可以看到强胰,返回Looper對象是調(diào)用ThreadLocal.get()舱沧,即當前線程對應的Looper對象。
我們可以根據(jù)此偶洋,判斷當前線程是否是主線程
public boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
另外熟吏,Handler也用來判斷當前線程是否有Looper,否則報錯Can't create handler inside thread that has not called Looper.prepare()
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
參考
任玉剛的《Android開發(fā)藝術探索》