Java ThreadLocal 類的知識點解讀

提及 Java 中的 ThreadLocal 類,可能不少安卓開發人員並非很熟悉,畢竟不多有使用到的地方。可是若是你仔細分析過 Handler 源碼的話,就必定見過這個類的出現。而 Handler 機制又是安卓知識體系中很是重要的一環,因此咱們有必要了解一下 ThreadLocal 類的相關知識點。java

ThreadLocal 使用簡介


顧名思義,ThreadLocal 必定與線程有關,而事實也是如此。ThreadLocal 做爲一個泛型類,解決的是線程內部對象訪問的問題,必定程度上避免對象做爲參數處處傳遞。一個線程經過 ThreadLocal 保存的泛型對象實例,其餘線程沒法訪問,固然也無需訪問。程序員

ThreadLocal 提供兩個對外方法:get() 和 set() 方法,分別用於內部對象的讀寫操做。編程

舉個例子,在工做線程中建立 ThreadLocal 對象,並存儲和讀取其字符串內容:微信

new Thread(new Runnable() {
    @Override
    public void run() {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("This is thread local variable");
        System.out.print(threadLocal.get());
    }
}).start();複製代碼

若是將上面例子中的建立語句提取到工做線程外面的其餘線程中,編譯器會自動報錯,提示訪問受限:多線程

Variable "threadLocal" is accessed from within inner class,needs to be declared final併發

ThreadLocal 工做原理


搞清楚 ThreadLocal 內部工做原理,能夠從前面使用簡介中提到的 get() 和 set() 兩個方法的源碼入手。ide

get() 方法源碼以下:oop

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();
}複製代碼

前面提到,ThreadLocal 與線程有關,從 get() 方法的源碼中便有所體現。使用當前線程實例做爲 getMap() 方法的參數,直接獲取用於保存 ThreadLocal 值的 ThreadLocalMap 對象。ui

getMap() 方法源碼很簡單,只有一行代碼:this

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}複製代碼

即直接讀取 Thread 類中定義的 ThreadLocalMap 變量:

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;複製代碼

再回到 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;
}複製代碼

也就是間接調用 createMap() 方法實現初始化操做:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}複製代碼

而且初始化時使用的默認值是由 initialValue() 方法提供的默認值 null:

protected T initialValue() {
    return null;
}複製代碼

該方法爲 protected 類型,這也爲開發人員使用時修改系統默認值 null 提供了可能。咱們只須要在建立 ThreadLocal 實例的時候或者在其子類中重寫 initialValue() 方法便可,好比:

ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "a new initial value";
    }
};複製代碼

看完 get() 方法源碼,再來看看 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() 方法一樣獲取的是當前線程中的 ThreadLocalMap 對象進行賦值操做。其中有一行代碼:

map.set(this, value);複製代碼

與 get() 方法中提到的初始化操做同樣,表示 ThreadLocalMap 使用 ThreadLocal 實例做爲 key、將要保存的對象做爲 value 這種鍵值對的形式保存。而 get() 方法讀取的時候也是如此。

Android 中的應用場景


做爲安卓開發人員,使用 ThreadLocal 的場景並非不少,然而咱們平時常常接觸的 Handler 機制中涉及到的 Looper 類,其內部便使用到 ThreadLocal 操做。

你們知道,一個線程中只能有一個 Looper 實例不停循環運轉訪問 MessageQueue 消息隊列。這個 Looper 對象實例即是藉助 ThreadLocal 保存在對應線程當中,Looper 類源碼定義以下:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();複製代碼

默認狀況下,工做線程中使用 Looper 以前必須調用 prepare() 方法初始化(系統在主線程自動幫咱們執行)。這個方法即是用來建立 Looper 對象並保存的:

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));
}複製代碼

因此,知道一些 ThreadLocal 的知識有助於咱們加深理解 Handler 原理。

ThreadLocal 答憂解惑


1,ThreadLocal 只能在線程內訪問嗎

其實否則。Java 還提供有一個繼承自 ThreadLocal 的 InheritableThreadLocal 類,解決跨線程訪問 ThreadLocal 變量的問題。

在 Thread 類中也定義有一個 inheritableThreadLocals 變量:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;複製代碼

當子線程建立的時候,自動使用父線程中 inheritableThreadLocals 變量保存的對象值初始化本身。(固然,該默認值也是能夠由開發人員修改的。)

private void init2(Thread parent) {
    // 省略不相關源碼
    if (parent.inheritableThreadLocals != null) {
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
                parent.inheritableThreadLocals);
    }
}複製代碼

有關 InheritableThreadLocal 的更多細節,感興趣的朋友能夠自行查閱相關源碼。

2,ThreadLocal 會引起內存泄漏嗎

答案是否認的。雖然從前面的源碼中能夠看出,ThreadLocal 保存的對象實例本質上歸屬於 Thread 所持有引用,當線程執行完畢,GC 自動回收其相關資源。

看上去,默認狀況下對象的生命週期與 Thread 生命週期一致,可是,ThreadLocalMap 在內部實現時對於 ThreadLocal 中對象的引用使用的是弱引用類型,從而規避內存泄漏的風險:

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }複製代碼

備註:對於 JVM 來講,內存泄漏的罪魁禍首在於強引用致使對象所佔用的內存沒法獲得釋放,而 Java 四種引用類型中的弱引用方式恰好可以解決這種問題。弱引用不會影響 GC 的垃圾回收工做,避免內存泄漏風險。同時也應當注意,使用弱引用獲取到的對象實例,在使用前,須要添加空值判斷。

3,ThreadLocal 與多線程併發的關係?

沒有關係!網上有些文章誤傳 ThreadLocal 是用來解決多線程併發的問題,這是錯誤的理解。經過前文一系列的使用和原理分析不難看出,ThreadLocal 用於線程讀寫各自內部對象,一般除本身以外的線程沒有必要也沒法訪問這個對象。

而多線程併發產生的臨界資源訪問問題,徹底能夠經過 synchronized 關鍵字實現的同步機制(也稱互斥鎖機制)解決。有關 Java 併發編程的知識點,能夠參考:

wiki.jikexueyuan.com/project/jav…

相關知識鏈

dzone.com/articles/st…

關於我:亦楓,博客地址:yifeng.studio/,新浪微博:IT亦楓

微信掃描二維碼,歡迎關注個人我的公衆號:安卓筆記俠

不只分享個人原創技術文章,還有程序員的職場遐想

相關文章
相關標籤/搜索