1. 程式人生 > >ThreadLocal的作用和實現原理

ThreadLocal的作用和實現原理

ThreadLocal的作用

ThreadLocal是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定的執行緒中可以獲取到儲存的資料,對於其他執行緒來說則無法取到資料。

ThreadLocal的主要作用

輕鬆實現一些看起來很複雜的功能,適合以下一些應用場景。

  • 應用場景1

某些資料是以執行緒為作用域並且不同執行緒具有不同的資料的副本時,就可以考慮用ThreadLocal

例如:Android中,Handler需要獲取當前執行緒的Looper,很顯然Looper的作用域是執行緒並且不同執行緒具有不同的Looper。

通過ThreadLocal

就可以輕鬆實現Looper線上程中的存取。

  • 應用場景2

複雜邏輯下的物件傳遞,比如監聽器的傳遞,有些時候一個執行緒中的任務過於複雜,我們又需要監聽器能夠貫穿整個執行緒的執行過程。

採用ThreadLocal可以讓監聽器作為執行緒內的全域性物件而存在,線上程內部只要通過get方法就可以獲取到監聽器。

ThreadLocal的使用示例

ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

mBooleanThreadLocal.set(true);
Log.d("@@@", "[[email protected]
]mBooleanThreadLocal = "
+ mBooleanThreadLocal.get()); new Thread("[email protected]") { @Override public void run() { mBooleanThreadLocal.set(false); Log.d("@@@", "[[email protected]]mBooleanThreadLocal = " + mBooleanThreadLocal.get()); } }.start(); new Thread("
[email protected]
"
) { @Override public void run() { Log.d("@@@", "[[email protected]]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的實現原理

ThreadLocal是一個泛型類,定義為public class ThreadLocal,只要弄清楚ThreadLocal的get方法和set方法,就可以明白它的實現原理。

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,然後根據key=當前ThreadLocal來獲取對應的value值。

從ThreadLocal的set和get方法可以看出,它們所操作的都是當前執行緒的ThreadLocalMap物件。

因此在不同執行緒中,訪問同一個ThreadLocal的set和get方法,它們對ThreadLocalde的讀、寫操作僅限於各自執行緒的內部,從而使ThreadLocal可以在多個執行緒中互不干擾地儲存和修改資料。

ThreadLocal在Android中的應用

獲取當前執行緒的Looper

在Looper類中,定義了一個ThreadLocal靜態常量

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

1、初始化Looper,即呼叫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物件是呼叫ThreadLocal.get(),即當前執行緒對應的Looper物件。

我們可以根據此,判斷當前執行緒是否是主執行緒

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開發藝術探索》