深入理解ThreadLocal

本文以android-26的源碼為基礎(chǔ)進行分析

Looper與ThreadLocal

首先我們從Looper的源碼開始
在使用Handler和Looper的時候,我們知道,Handler發(fā)送消息給Looper,加入其消息隊列,Looper則不斷循環(huán)去除隊列前端的消息,并執(zhí)行Handler中的回調(diào)代碼。

但是Looper在使用前需要先執(zhí)行Looper.prepare()操作,否則會報如下的錯誤: Can't create handler inside thread that has not called Looper.prepare(),因為此時Looper對象并沒有初始化

那么我們來看一下Looper.prepare()做了什么

public final class Looper {

   public static void prepare() {
        prepare(true);
       }

    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));
    }
}

從sThreadLocal.set(new Looper(quitAllowed)) 這一句代碼我們發(fā)現(xiàn), Looper.prepare()當中new了一個Looper對象,并將這個Looper對象設(shè)置進了一個叫做的東西里去,我們想要取當前線程的Looper對象的話,可以直接調(diào)用Looper.myLooper()這個靜態(tài)方法。

public final class Looper {
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
 }

來看一下sThreadLocal的定義:

public final class Looper {

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
}

我們發(fā)現(xiàn)這個sTheadLocal是Looper中的一個靜態(tài)對象,類型為ThreadLocal,接下來我們需要解釋一下ThreadLocal是個什么東西

ThreadLocal

ThreadLocal的一個特點就是線程間互相隔離,在每個線程取同一個ThreadLocal對象都會得到一個不同的值。例如Looper, 每個線程的都只有一個Looper,且在各個線程中調(diào)用Looper.myLooper()的方法都會返回其自己的Looper。

在上面我們看到Looper.myLooper()方法通過調(diào)用sThread.get()方法獲取到了自己對應(yīng)的Looper,那么我們來看一下ThreadLocal的get()的方法。

public class ThreadLocal<T> {

       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對象,然后得到了當前Thread對象中的ThreadLocalMap對象,以當前的ThreadLocal對象為key獲取到ThreadLocalMap存的value,在這個例子中這個value就是Looper啦。

暫停一下,咱們捋一捋,首先Looper通過其中的靜態(tài)變量sThreadLocal.get()方法獲取到當前線程的ThreadLocalMap對象threadLocals 。然后又以sThreadLocal這個對象本身為key取到了threadLocals存著的Looper對象。

看著好像有點繞,那么我們換一種說法,每個Thread都存了一個map(實際是個數(shù)組),可以以Looper中的靜態(tài)對象sThreadLocal為key,在這個map中取到這個線程的Looper。由于sThreadLocal是個靜態(tài)對象,所以對于任意一個線程,這個key是固定的,這樣一來,通過Thread.currentThread()獲取到了某個線程就等于獲取到了其中的Looper對象。

這么一來是不是就清晰了很多,Looper中之所以要定義這么一個sThreadLocal的靜態(tài)對象,實際上就相當于定義了一個全局存在的static final的key,只不過這個key是ThreadLocal對象而已。

/** 本文不會對ThreadLocalMap進行更深入的分析,各位可以自行閱讀源碼去查看其具體實現(xiàn)**/

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

最后我們稍微圓一圓整個流程,現(xiàn)在我們知道了我們獲取到了一個Thread,就能以sThreadLocal為key get到其中的Looper對象。那么我們是什么這個Looper對象set到對應(yīng)的Thread當中的呢。記不記得文章開頭Looper.prepare()的過程,其中調(diào)用了sTheadLocal.set(T)方法,最后是怎么將Looper對象綁定到線程上的,來看一下源碼:

public class ThreadLocal<T> {

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    }

首先獲取到當前線程t,然后得到其中的ThreadLocalMap對象,然后將這個Looper對象放到了map中對應(yīng)sThreadLocal的地方。相信這里大家都能很輕松的理解了。

最后總結(jié)一下ThreadLocal存在的意義:
  • 封裝了對當前所在線程中ThreadLocalMap的set和get操作,默認以自身作為key值進行數(shù)據(jù)存儲
  • 以泛型對外暴露,支持了所有數(shù)據(jù)類型

因此在使用ThreadLocal的時候,每一個數(shù)據(jù)都需要新建一個ThreadLocal對象為key
例如針對每一個線程都要存儲一個String,需要新建一個ThreadLocal<String>對象,如果希望在任何地方都能夠獲取到這個String,則該ThreadLocal<String>對象需要是用static進行修飾。

ThreadLocal.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容