ThreadLocal 機(jī)制解析

ThreadLocal

在Android開(kāi)發(fā)中发侵,Handler消息處理機(jī)制中用到了ThreadLocal類(lèi)锨络,花了點(diǎn)時(shí)間對(duì)它進(jìn)行解析绢片。

Thread類(lèi)中有個(gè)成員變量threadLocals,類(lèi)型為T(mén)hreadLocal.ThreadLocalMap乔外,是ThreadLocal自定義的一個(gè)hashmap横媚,它的key是ThreadLocal<?>類(lèi)型纠炮,value是Object。如下:

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

注意:在Thread中threadLocals參數(shù)并沒(méi)有被賦值灯蝴,所以默認(rèn)為null

ThreadLocal類(lèi)中恢口,有這樣一個(gè)函數(shù)

    //初始化值
    protected T initialValue() {
        return null;
    }

這個(gè)函數(shù)用來(lái)初始化在一個(gè)線(xiàn)程中的初始值,默認(rèn)返回null穷躁,建議使用ThreadLocal時(shí)重寫(xiě)耕肩。

再看看這個(gè)函數(shù)

    /**
     * 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;
    }
    /**
     * 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;
    }

從setInitialValue()方法名可以看出是設(shè)置ThreadLocal的初始值,先是獲取當(dāng)前線(xiàn)程 t 问潭,然后通過(guò)getMap(t)方法獲取當(dāng)前線(xiàn)程 t 的threadLocals變量猿诸,就是一個(gè)ThreadLocalMap實(shí)例,通過(guò)上面的getMap(Thread t)方法看出返回的map應(yīng)該是null狡忙,所以執(zhí)行createMap(t,value)方法梳虽。

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

這個(gè)方法很關(guān)鍵,通過(guò)createMap()方法使Thread的成員變量threadLocals賦值了灾茁,并把this當(dāng)做key窜觉,把通過(guò)initialValue()方法返回的值作為value(所以重寫(xiě)initialValue()的話(huà)就避免了初始化值為null的尷尬)。

同時(shí)我們發(fā)現(xiàn)在ThreadLocal類(lèi)中的set()方法也調(diào)用了createMap()方法北专,是不是有種頓時(shí)豁然開(kāi)朗的感覺(jué)禀挫。

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

我們先看看哪里調(diào)用了setInitialValue()法

    /**
     * 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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

發(fā)現(xiàn)整個(gè)ThreadLocal類(lèi)中只有這一個(gè)地方調(diào)用了這個(gè)方法⊥赝牵看了這里就應(yīng)該明白了吧语婴。

  • 在沒(méi)有調(diào)用set()方法的情況下, 如果第一次調(diào)用get()方法,getMap()肯定返回一個(gè)null的ThreadLocalMap對(duì)象腻格,就會(huì)執(zhí)行 return setInitialValue(); 語(yǔ)句画拾,返回初始化的值。
  • 如果調(diào)用了set()方法的情況下菜职,可以看出set方法也調(diào)用了createMap(t, value);來(lái)給Thread的成員變量threadLocals賦值,那么getMap()肯定就返回一個(gè)不為null的ThreadLocalMap對(duì)象旗闽,把this作為對(duì)象來(lái)獲取value酬核。

小結(jié)

  • Thead中有一個(gè)類(lèi)型為T(mén)hreadLocalMap的成員變量threadLocals,并且初始值為null
  • ThreadLocalMap是一個(gè)ThreadLocal自定義的HashMap适室,鍵為T(mén)hreadLocal<?>嫡意,值為Object
  • ThreadLocal默認(rèn)會(huì)有一個(gè)初始值null,你可以通過(guò)重寫(xiě)initialValue()方法或者調(diào)用set()方法來(lái)改變這個(gè)初始值俱两,他們的本質(zhì)都是去調(diào)用createMap()方法給當(dāng)前的線(xiàn)程Thread的成員變量threadLocals賦值跃洛。
  • ThreadLocal的set()方法通過(guò)獲取當(dāng)前線(xiàn)程 t 厅各,通過(guò) t 的成員變量threadLocals的set()方法來(lái)去保存value值,并把當(dāng)前對(duì)象this作為key旧巾。
  • 調(diào)用ThreadLocal的get()方法來(lái)獲取value,實(shí)質(zhì)就是獲取當(dāng)前線(xiàn)程t的成員變量threadLocals忍些,并且把自身作為key來(lái)獲取value的過(guò)程鲁猩。

每個(gè)Thread都維護(hù)了一個(gè)ThreadLocalMap對(duì)象,也就是threadLocals變量罢坝,通過(guò)它就可以保存各種不同類(lèi)型的ThreadLocal和對(duì)應(yīng)的值

Demo示例:

public class ThreadStudy {

    private static OneThread oneThread;
    private static TwoThread twoThread;
    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 99;
        }
    };
    private static ThreadLocal<String> stringThreadLocal = new InheritableThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "Hello world";
        }
    };

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        oneThread = new ThreadStudy().new OneThread();
        twoThread = new ThreadStudy().new TwoThread();
        oneThread.start();
        twoThread.start();

    }

    class OneThread extends Thread{

        public OneThread(){
        }

        @Override
        public void run() {
            //給Thread的threadLocals變量賦值廓握,并把stringThreadLocal作為key,"設(shè)置value值"作為value保存在threadLocals里嘁酿。
            stringThreadLocal.set("設(shè)置value值");
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }

    class TwoThread extends Thread{

        public TwoThread(){
        }
        @Override
        public void run() {
            //由于沒(méi)有調(diào)用set方法隙券,所以會(huì)在第一次調(diào)用get()方法中去個(gè)給Thread的threadLocals變量賦值
            System.out.println(Thread.currentThread().getName() + "  " + integerThreadLocal.get());
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }
}

輸出結(jié)果:

Thread-0 設(shè)置value值
Thread-1 99
Thread-1 Hello world

相信看到這里對(duì)ThreadLocal都理解了吧。

話(huà)說(shuō)ThreadLocalMap類(lèi)到底怎么工作的呢闹司,下面我們一起來(lái)看看娱仔。

ThreadLocalMap

在ThreadLocalMap中有個(gè)靜態(tài)內(nèi)部類(lèi)Entry

        /**
         * 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;
            }
        }

Entry繼承了WeakReference(弱引用)類(lèi),應(yīng)該這里準(zhǔn)確的說(shuō)是ThreadLocal作為Entry的key并成了弱引用开仰。

其實(shí)在HashMap中也有這樣的一個(gè)同名的接口類(lèi)拟枚,關(guān)系是:HashMap<K,V> 繼承了AbstractMap<K,V> ,然后AbstractMap<K,V> 實(shí)現(xiàn)了 Map<K,V>众弓,在Map<K,V>接口中就有了Entry<K,V>接口恩溅。

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

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

ThreadLocalMap和HashMap一樣默認(rèn)容量16,并且用數(shù)組數(shù)組實(shí)現(xiàn)谓娃。在ThreadLoca中通過(guò)set方法類(lèi)添加數(shù)據(jù)脚乡,從源碼中可以看出實(shí)際是調(diào)用了ThreadLocalMap類(lèi)的set方法,我們就先來(lái)看看set方法是怎么實(shí)現(xiàn)的吧。

        /**
         * 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();
        }
int i = key.threadLocalHashCode & (len-1);

通過(guò)ThreadLocal的threadLocalHashCode 參數(shù)奶稠,可以理解為HashCode(int類(lèi)型hash值俯艰,每個(gè)ThreadLocal對(duì)應(yīng)一個(gè)),和table數(shù)組的長(zhǎng)度-1的差做“與”運(yùn)算得到元素在數(shù)組中的下標(biāo)锌订。然后從table數(shù)組中取出對(duì)應(yīng)下標(biāo)的Entry判斷是否為null竹握,如果沒(méi)有發(fā)生沖突(取出的Entry == null)則給對(duì)應(yīng)的下標(biāo)賦值。如果發(fā)生了沖突(取出的Entry != null)辆飘,則比較沖突的Entry的key是否和當(dāng)前的set(ThreadLoacal key,Object value)的參數(shù)key相同啦辐,如果是相同的則覆蓋原來(lái)的value并結(jié)束。如果參數(shù)key和獲取的Entry的key不相等蜈项,這個(gè)時(shí)候就需要解決沖突芹关,這里是通過(guò)向后移動(dòng)下標(biāo),即下標(biāo) +1(這里和HashMap解決沖突不同)來(lái)解決的紧卒。

解決沖突如下:

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

那么ThreadLocalMap添加數(shù)據(jù)的過(guò)程完成了侥衬。ThreadLocal中通過(guò)get方法取出保存的數(shù)據(jù),從源碼中也可以看出實(shí)際是調(diào)用了ThreadLocalMap的getEntry方法跑芳。

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

getEntry先是通過(guò)int i = key.threadLocalHashCode & (table.length - 1);取得對(duì)應(yīng)的下標(biāo)轴总,和前面的set方法的獲取下標(biāo)方式相對(duì)應(yīng)。如果取到的Entry值不為null而且key也相同就返回取到的Entry聋亡。由于在添加Entry的時(shí)候有可能發(fā)生沖突肘习,那么在取得時(shí)候就可能不能一次性通過(guò)下標(biāo)取到對(duì)應(yīng)的值,如果發(fā)生這樣的情況就調(diào)用
getEntryAfterMiss()來(lái)獲取坡倔。


        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

這里必須和前面解決沖突的思路一致漂佩,如果沒(méi)有一次性取到對(duì)應(yīng)的Entry,下標(biāo)就向后移動(dòng)(+1)罪塔,然后在取出新的下標(biāo)的值進(jìn)行比較投蝉,如果符合條件就返回。如果取出的Entry為null征堪,則返回null瘩缆。

默認(rèn)的ThreadLocalMap容量只有16,如果存放的數(shù)據(jù)多了佃蚜,那么就跟HashMap一樣需要擴(kuò)容庸娱,默認(rèn)情況下當(dāng)存儲(chǔ)的數(shù)據(jù)量超過(guò)容量的2/3的時(shí)候就會(huì)擴(kuò)容為之前容量的2倍。

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
       /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

相信你看到這里對(duì)ThreadLocal有更深入的理解了吧谐算。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熟尉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洲脂,更是在濱河造成了極大的恐慌斤儿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異往果,居然都是意外死亡疆液,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)陕贮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堕油,“玉大人,你說(shuō)我怎么就攤上這事肮之♀善” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵局骤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我暴凑,道長(zhǎng)峦甩,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任现喳,我火速辦了婚禮凯傲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗦篱。我一直安慰自己冰单,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布灸促。 她就那樣靜靜地躺著诫欠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浴栽。 梳的紋絲不亂的頭發(fā)上荒叼,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音典鸡,去河邊找鬼被廓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛萝玷,可吹牛的內(nèi)容都是我干的嫁乘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼球碉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜓斧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汁尺,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤法精,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搂蜓,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狼荞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帮碰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片相味。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖殉挽,靈堂內(nèi)的尸體忽然破棺而出丰涉,到底是詐尸還是另有隱情,我是刑警寧澤斯碌,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布一死,位于F島的核電站,受9級(jí)特大地震影響傻唾,放射性物質(zhì)發(fā)生泄漏投慈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一冠骄、第九天 我趴在偏房一處隱蔽的房頂上張望伪煤。 院中可真熱鬧,春花似錦凛辣、人聲如沸抱既。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)防泵。三九已至,卻和暖如春跋理,著一層夾襖步出監(jiān)牢的瞬間择克,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工前普, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肚邢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓拭卿,卻偏偏與公主長(zhǎng)得像骡湖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峻厚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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