Android 消息機制: ThreadLocal

前言

ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定的線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據

Android 的消息機制中,每個線程的 Looper 實例都是不同的,各個線程之間的 Looper 都在獨立的輪詢消息隊列,它們之間沒有任何的交集,互不干擾,這其實就是通過 ThreadLocal 來實現的:

public static @Nullable Looper myLooper() {
    //從ThreadLocal 中獲取 Looper 實例
    return sThreadLocal.get();
}

何時可以使用 ThreadLocal?下面借用《Android開發藝術探索》中的一句話來說明:

一般來說,當某些數據是以線程爲作用域並且不同線程具有不同的數據副本的時候,就可以考慮採用 ThreadLocal

下面通過實際的例子來演示 ThreadLocal 的含義:

/**
 * @author: created by jonsonhao
 * @date: 2020-03-28 14:56
 * @description: ThreadLocal Demo
 */
public class ThreadLocalTest {

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

    public static void main(String[] args) {
        mBooleanThreadLocal.set(true);
        System.out.println("[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

        new Thread("Thread#1") {

            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                System.out.println("[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                System.out.println("[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();
    }

}

運行結果:

[Thread#main]mBooleanThreadLocal=true
[Thread#1]mBooleanThreadLocal=false
[Thread#1]mBooleanThreadLocal=null

可以看到,雖然我們在不同線程中調用的是同一個 ThreadLocal 對象,但是它們通過 ThreadLocal 獲取到的值卻是不一樣的。接下來我們看看 ThreadLocal 內部是如何實現的吧,通過源碼瞭解 ThreadLocal 是如何做到同一個實例,維護着不同線程的數據副本的

1. 源碼分析

1.1 ThreadLocal

ThreadLocal 是一個泛型類,支持存儲各種數據類型,它對外暴露的方法很少,基本只有下面三個:

set()
get()
remove()

1.1.1 set()

首先,我們先看一下 ThreadLocal 是如何把數據存儲進去的:

// ThreadLocal#set()
public void set(T value) {
    //1.獲取當前線程
    Thread t = Thread.currentThread();
    //2.以當前線程作爲參數,獲取一個 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3.map 不爲空,則直接把數據存進去
        map.set(this, value);
    else
        //4.map 爲空,則創建一個 map
        createMap(t, value);
}

從上面的代碼可以看到,ThreadLocal 其實只是一個操作類,而內部真正存儲數據的類是 ThreadLocalMap 這個容器類。接下來看一下 getMap() 方法:

//ThreadLocal#getMap()
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

getMap() 方法直接返回線程的成員變量 threadLocals,這個變量是 ThreadLocalMap 類型的。因爲 ThreadLocal 和 Thread 是在同一個包下,所以 ThreaLocal 可以直接訪問 Thread 類的包訪問權限的成員變量 threadLocals,並且 Thread 類中的 threadLocals 默認是 null 的,在 Thread 類中沒有任何賦值的地方,那它在哪裏賦值呢?我們看一下 createMap() 方法:

//ThreadLocal#createMap()
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

也就是說,threadLocals 只有在 ThreadLocal 中的 createMap() 方法中賦值

以上,set 方法就分析完了,從這個方法中我們可以瞭解到,ThreadLocal 其實是封裝了對 Thread 中 ThreadLocalMap 屬性的操作。ThreaLocalMap 就是一個類似 HashMap 的容器類(不過它和 HashMap 不同)。接下來我們看一下 get() 方法:

1.1.2 get()

//ThreadLocal#get()
public T get() {
    //1.獲取當前線程實例
    Thread t = Thread.currentThread();
    //2.以當前線程爲參數,獲取 ThreadLocal
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //3.map 不爲空,則以當前 ThreadLocal 實例爲key參數,從map中獲取值
        ThreadLocalMap.Entry e = map.getEntry(this);
        //有找到值則直接返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4.如果map爲空或map中取不到值,則返回默認值
    return setInitialValue();
}

同樣的,get 方法也是先獲取到當前線程實例後拿到線程的 ThreadLocalMap 對象,然後從中獲取值。當遇到第4步的情況時會調用 setInitialValue()方法。接着,我們往下看 setInitialValue 方法:

//ThreadLocal#setInitialValue()
private T setInitialValue() {
    //獲取默認值
    T value = initialValue();
    //獲取當前線程對象
    Thread t = Thread.currentThread();
    //以當前線程爲參數,獲取 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    //返回默認值
    return value;
}

//ThreadLocal#initialValue()
protected T initialValue() {
    return null;
}

setInitialValue() 除了調用 initialValue() 獲取默認值然後返回之外,其他代碼邏輯基本和 set() 方法一致。initialValue 默認返回是 null,這個方法用戶是可以重寫的,然後返回你想要返回的默認值。

1.1.3 remove()

public void remove() {
    //根據當前線程實例獲取 ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // map 不爲空就根據 key 移除對應的值
        m.remove(this);
}

到這裏是不是對 ThreadLocal 的原理有了較爲深刻的理解了。**ThreadLocal 是如何維護不同線程的數據副本的?原來,這些數據本來就是存儲在各自的線程當中的。**接下來,我們去揭開 ThreadLocalMap 的面紗,驗證一下,ThreadLocalMap 是不是一個用於存儲數據的容器類:

1.2 ThreadLocalMap

ThreadLocalMap 其實是 ThreaLocal 的靜態內部類,看源碼可以發現,其內部數據結構其實就是一個哈希表,初始容量爲16,數組元素爲 Entry

//ThreadLocal#ThreadLocalMap
static class ThreadLocalMap {

    //Entry
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    private Entry[] table;
    //初始容量
    private static final int INITIAL_CAPACITY = 16;
    private int size = 0;

    private void set(ThreadLocal key, Object value) {
        ...
    }

    private Entry getEntry(ThreadLocal key) {
        ...   
    }
}

Entry 繼承了 WeakReference<ThreadLocal<?>>,我們可以把它看成一個健值對,健是當前 ThreaLocal 對象,值是存儲的對象

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章