Android 之 ThreadLocal簡析

前言

源碼 基于 Android SDK 28 JDK 1.8


說起 ThreadLocal软吐,大家可能會比較陌生鹉戚,但是如果想要比較好地理解 Android 的消息機制糊治,ThreadLocal 是必須要掌握的碌燕,這是因為 Looper 的工作原理恒水,就跟 ThreadLocal 有很大的關(guān)系咒劲,理解 ThreadLocal 的實現(xiàn)方式有助于我們理解 Looper 的工作原理顷蟆,這篇文章就從 ThreadLocal 的用法講起,一步一步帶大家理解 ThreadLocal腐魂。

一帐偎、ThreadLocal 是什么


ThreadLocal 是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在 指定的線程中 存儲數(shù)據(jù)蛔屹,數(shù)據(jù)存儲以后削樊,只有在指定線程中可以獲取到存儲的數(shù)據(jù),對于其他線程來說則無法獲取到數(shù)據(jù)兔毒。

一般來說漫贞,當某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,就可以考慮采用 ThreadLocal育叁。

二迅脐、基本用法


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

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

set 方法

mBooleanThreadLocal.set(false);

get 方法

mBooleanThreadLocal.get()

接下來用一個完整的例子豪嗽,幫助大家理解 ThreadLocal



public class MainActivity extends AppCompatActivity {

    String TAG = "ThreadLocal";
    ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBooleanThreadLocal.set(true);
        Log.d(TAG, "Current Thread: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
        new Thread("Thread#1") {
            @Override
            public void run() {
                super.run();
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "Thread 1: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
            }
        }.start();
        new Thread("Thread#2") {
            @Override
            public void run() {
                super.run();
                Log.d(TAG, "Thread 2: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
            }
        }.start();

    }
}

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


從上面的日志中可以看出肯适,雖然在不同的線程中訪問的是同一個 ThrealLocal 對象变秦,但是它們通過 ThrealLocal 獲取到的值確實不一樣的成榜,這就是 ThrealLocal 的奇妙之處了。

ThrealLocal 之所以有這么奇妙的效果蹦玫,就是因為不同線程訪問同一個 ThrealLocal 的 get() 方法赎婚,ThrealLocal 內(nèi)部都會從各自的線程中取出一個數(shù)組,然后再從數(shù)組中根據(jù)當前 ThrealLocal 的索引去查找不同的 value 值樱溉。

三挣输、ThrealLocal 工作原理


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

1、ThrealLocal 的 set() 方法

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    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(Thread t) 方法獲取一個 ThreadLocalMap,如果這個 map 不為空的話拇舀,就將 ThrealLocal 和 我們想存放的 value 設(shè)置進去逻族,不然的話就創(chuàng)建一個 ThrealLocalMap 然后再進行設(shè)置。

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

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 中進行存儲的抠璃。
首先我們先看一下set方法中的

map.set(this, value);

對應(yīng)源碼為:

/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

可以看到set方法中會獲取Entry對象,然后判斷當前threadlocal對象是否存儲在table數(shù)組中脱惰,若存在則更新value鸡典,若不存在,則新增一個Entry對象'''new Entry(key, value);'''存儲threadlocal和對應(yīng)的值枪芒,然后加入到table數(shù)組中彻况。
在 threadLocals 內(nèi)部有一個數(shù)組,private Entry[] table舅踪,ThrealLocal 的值就存在這個 table 數(shù)組中纽甘。


        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;


2、ThrealLocal 的 get() 方法

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

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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() 方法的邏輯也比較清晰,它同樣是取出當前線程的 threadLocals 對象左权,如果這個對象為 null皮胡,就調(diào)用 setInitialValue() 方法

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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() 的值賦給我們想要的值赏迟,默認情況下屡贺,initialValue() 的值為 null,當然也可以重寫這個方法锌杀。

   /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the <tt>initialValue</tt> method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns <tt>null</tt>; if the
     * programmer desires thread-local variables to have an initial
     * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

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

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

總結(jié)

ThreadLocal 是線程內(nèi)部的數(shù)據(jù)存儲類,每個線程中都會保存一個 ThreadLocal.ThreadLocalMap threadLocals = null;猾担,ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類袭灯,里面保存了一個 private Entry[] table 數(shù)組,這個數(shù)組就是用來保存 ThreadLocal 中的值垒探。通過這種方式妓蛮,就能讓我們在多個線程中互不干擾地存儲和修改數(shù)據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末圾叼,一起剝皮案震驚了整個濱河市蛤克,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夷蚊,老刑警劉巖构挤,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惕鼓,居然都是意外死亡筋现,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門箱歧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矾飞,“玉大人,你說我怎么就攤上這事呀邢∪髀伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵价淌,是天一觀的道長申眼。 經(jīng)常有香客問我瞒津,道長,這世上最難降的妖魔是什么括尸? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任巷蚪,我火速辦了婚禮,結(jié)果婚禮上濒翻,老公的妹妹穿的比我還像新娘屁柏。我一直安慰自己,他們只是感情好肴焊,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布前联。 她就那樣靜靜地躺著功戚,像睡著了一般娶眷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啸臀,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天届宠,我揣著相機與錄音,去河邊找鬼乘粒。 笑死豌注,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的灯萍。 我是一名探鬼主播轧铁,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旦棉!你這毒婦竟也來了齿风?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绑洛,失蹤者是張志新(化名)和其女友劉穎救斑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體真屯,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡脸候,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绑蔫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片运沦。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖配深,靈堂內(nèi)的尸體忽然破棺而出携添,到底是詐尸還是另有隱情,我是刑警寧澤凉馆,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布薪寓,位于F島的核電站亡资,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏向叉。R本人自食惡果不足惜锥腻,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望母谎。 院中可真熱鬧瘦黑,春花似錦、人聲如沸奇唤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咬扇。三九已至甲葬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間懈贺,已是汗流浹背经窖。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梭灿,地道東北人画侣。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像堡妒,于是被迫代替她去往敵國和親配乱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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