Android 一起來(lái)看看 ThreadLocal

前言

說(shuō)起 ThreadLocal,大家可能會(huì)比較陌生,但是如果想要比較好地理解 Android 的消息機(jī)制,ThreadLocal 是必須要掌握的,這是因?yàn)?Looper 的工作原理,就跟 ThreadLocal 有很大的關(guān)系,理解 ThreadLocal 的實(shí)現(xiàn)方式有助于我們理解 Looper 的工作原理,這篇文章就從 ThreadLocal 的用法講起,一步一步帶大家理解 ThreadLocal。

一、ThreadLocal 是什么


ThreadLocal 是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在 指定的線程中 存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。

一般來(lái)說(shuō),當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用 ThreadLocal。

二、基本用法


創(chuàng)建,支持泛型

ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

set 方法

mStringThreadLocal.set("developerHaoz");

get 方法

mStringThreadLocal.get();

接下來(lái)用一個(gè)完整的例子,幫助大家理解 ThreadLocal

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBooleanThreadLocal.set(true);
        Log.d(TAG, "Current Thread: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());
        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "Thread 1: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());

            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                Log.d(TAG, "Thread 2: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());

            }
        }.start();
    }

在上面的代碼中,在主線程中設(shè)置 mBooleanThrealLocal 的值為 true,在子線程 1 中設(shè)置為 false,在子線程 2 中不設(shè)置 mBooleanThrealLocal 的值,然后分別在 3 個(gè)線程中通過(guò) get() 方法獲取 mBooleanThrealLocal 的值

image.png

從上面的日志中可以看出,雖然在不同的線程中訪問(wèn)的是同一個(gè) ThrealLocal 對(duì)象,但是它們通過(guò) ThrealLocal 獲取到的值確實(shí)不一樣的,這就是 ThrealLocal 的奇妙之處了。

ThrealLocal 之所以有這么奇妙的效果,就是因?yàn)椴煌€程訪問(wèn)同一個(gè) ThrealLocal 的 get() 方法,ThrealLocal 內(nèi)部都會(huì)從各自的線程中取出一個(gè)數(shù)組,然后再?gòu)臄?shù)組中根據(jù)當(dāng)前 ThrealLocal 的索引去查找不同的 value 值。

三、ThrealLocal 工作原理


ThrealLocal 是一個(gè)泛型類,它的定義為 public class ThrealLocal<T>,只要弄清楚 ThrealLocal 的 set() 和 get() 方法就可以明白它的工作原理了。

1、ThrealLocal 的 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() 方法中,先獲取到當(dāng)前線程,然后通過(guò) getMap(Thread t) 方法獲取一個(gè) ThreadLocalMap,如果這個(gè) map 不為空的話,就將 ThrealLocal 和 我們想存放的 value 設(shè)置進(jìn)去,不然的話就創(chuàng)建一個(gè) ThrealLocalMap 然后再進(jìn)行設(shè)置。

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

ThreadLocalMap 其實(shí)是 ThreadLocal 里面的靜態(tài)內(nèi)部類,而每一個(gè) Thread 都有一個(gè)對(duì)應(yīng)的 ThrealLocalMap,因此獲取當(dāng)前線程的 ThrealLocal 數(shù)據(jù)就變得異常簡(jiǎn)單了。

public class Thread implements Runnable {

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

下面看一下,ThrealLocal 的值到底是如何在 threadLocals 中進(jìn)行存儲(chǔ)的。在 threadLocals 內(nèi)部有一個(gè)數(shù)組,private Entry[] table,ThrealLocal 的值就存在這個(gè) table 數(shù)組中。

    static class ThreadLocalMap {

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

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        private Entry[] table;
}

2、ThrealLocal 的 get() 方法

上面分析了 ThreadLocal 的 set() 方法,這里分析它的 get() 方法,代碼如下

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

可以發(fā)現(xiàn),ThrealLocal 的 get() 方法的邏輯也比較清晰,它同樣是取出當(dāng)前線程的 threadLocals 對(duì)象,如果這個(gè)對(duì)象為 null,就調(diào)用 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;
    }

在 setInitialValue() 方法中,將 initialValue() 的值賦給我們想要的值,默認(rèn)情況下,initialValue() 的值為 null,當(dāng)然也可以重寫這個(gè)方法。

    protected T initialValue() {
        return null;
    }

如果 threadLocals 對(duì)象不為 null 的話,那就取出它的 table 數(shù)組并找出 ThreadLocal 的 reference 對(duì)象在 table 數(shù)組中的位置。

從 ThreadLocal 的 set() 和 get() 方法可以看出,他們所操作的對(duì)象都是當(dāng)前線程的 threalLocals 對(duì)象的 table 數(shù)組,因此在不同的線程中訪問(wèn)同一個(gè) ThreadLocal 的 set() 和 get() 方法,他們對(duì) ThreadLocal 所做的 讀 / 寫 操作權(quán)限僅限于各自線程的內(nèi)部,這就是為什么可以在多個(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)。

總結(jié)

ThreadLocal 是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,每個(gè)線程中都會(huì)保存一個(gè) ThreadLocal.ThreadLocalMap threadLocals = null;,ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類,里面保存了一個(gè) private Entry[] table 數(shù)組,這個(gè)數(shù)組就是用來(lái)保存 ThreadLocal 中的值。通過(guò)這種方式,就能讓我們?cè)诙鄠€(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容