Android線程篇(十)之面試必問ThreadLocal

Java面試必問ThreadLocal捌浩,所以想必大家對ThreadLocal并不陌生右犹,從字面意思來看ThreadLocal很容易理解,但是想要真正理解并沒有那么容易,今天我們就來扒下它的外衣……

1.首先我們來看看ThreadLocal如何使用掉分,它能解決什么樣的問題

ThreadLocal赴恨,線程本地變量晤郑,它可以為變量在每個(gè)線程中都創(chuàng)建一個(gè)副本负蚊,但它本身能夠被多個(gè)線程共享使用,并且又能夠達(dá)到線程安全的目的浆兰,且絕對線程安全磕仅。

來來來珊豹,F(xiàn)or example:

 ThreadLocal<String> strLocal1=new ThreadLocal<>();
    public void setValue(){
        strLocal1.set(Thread.currentThread().getName());
    }
    public String getStrLocal1(){
        return strLocal1.get();
    }

    public void Run(){
        setValue();
        System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
        Thread thread=new Thread(){
            @Override
            public void run() {
                super.run();
                setValue();
                System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
            }
        };
        thread.start();
        System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
    }

看看Log:

<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4

從這段代碼的輸出結(jié)果可以看出,在main線程中和Thread-4線程中榕订,strLocal1保存的副本值都不一樣平夜。最后一次在main線程再次打印副本值是為了證明在main線程中和Thread-4線程中的副本值確實(shí)是不同的。

ThreadLocal存儲(chǔ)的值卸亮,在每個(gè)線程中互不影響,是不是很容易就實(shí)現(xiàn)線程安全玩裙。

我們來看下ThreadLocal的源碼兼贸,扒下它的遮羞布

先來看看ThreadLocal提供的幾個(gè)方法:

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

get() 方法第一句是取得當(dāng)前線程,然后通過getMap(t)方法獲取到一個(gè)map吃溅,map的類型為ThreadLocalMap溶诞。然后接著下面獲取到<key,value>鍵值對,如果獲取成功决侈,則返回value值螺垢。如果map為空,則調(diào)用setInitialValue方法返回value赖歌。

注意:ThreadLocalMap.Entry e = map.getEntry(this);

這里傳進(jìn)去的是this,也就是ThreadLocal枉圃。

來看看getMap()方法中干了什么:

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

在getMap中,是調(diào)用當(dāng)期線程t庐冯,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals孽亲。

那么我們繼續(xù)去Thread類中取看一下成員變量threadLocals是什么:

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

實(shí)際上就是一個(gè)ThreadLocalMap,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類展父,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn):

 /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

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

可以看到ThreadLocalMap的Entry繼承了WeakReference返劲,并且使用ThreadLocal作為鍵值。

WeakReference是個(gè)什么東西呢栖茉?

它就是Java里面?zhèn)髡f的:弱引用篮绿,弱引用用來描述非必需對象的,當(dāng)JVM進(jìn)行垃圾回收時(shí)吕漂,無論內(nèi)存是否充足亲配,都會(huì)回收被弱引用關(guān)聯(lián)的對象。在java中痰娱,用java.lang.ref.WeakReference類來表示弃榨,對于弱引用我們這里就不過多的講解了,有時(shí)間我們也來扒下它神秘的外衣梨睁。

ThreadLocalMap中ThreadLocal做為key被保存在了WeakReference中鲸睛,這就說明ThreadLocal在沒有外部強(qiáng)引用時(shí),發(fā)生GC時(shí)會(huì)被回收坡贺。

然后再繼續(xù)看setInitialValue方法的具體實(shí)現(xiàn):

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

很容易了解官辈,就是如果map不為空箱舞,就設(shè)置鍵值對,為空拳亿,再創(chuàng)建Map晴股。

先來看一下 T value = initialValue()

 /**
     * 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 {@code initialValue} 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 {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} 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;
    }

這里會(huì)返回null,會(huì)報(bào)空指針,所以我么在get()的時(shí)候肺魁,必須先set()
再來电湘,看一下createMap的實(shí)現(xiàn):

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

再來看看這一行:
t.threadLocals = new ThreadLocalMap(this, firstValue);

  * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:

首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals鹅经,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的寂呛,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)瘾晃。

初始化時(shí)贷痪,在Thread里面,threadLocals為空蹦误,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法劫拢,就會(huì)對Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值强胰,以ThreadLocal要保存的副本變量為value舱沧,存到threadLocals。
再來看看ThreadLocal里面的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);
    }

和setInitialValue()方法一樣偶洋,如果map不為空狗唉,就設(shè)置鍵值對,為空涡真,再創(chuàng)建Map分俯,createMap(t, value)我們上面已經(jīng)分析過了。

好了哆料,該到總結(jié)的時(shí)候了:

1.通過ThreadLocal創(chuàng)建的副本缸剪,存儲(chǔ)在每個(gè)線程自己的threadLocals中。

  1. threadLocals實(shí)際就是ThreadLocalMap东亦,ThreadLocalMap把ThreadLocal做為key杏节。

3.在進(jìn)行g(shù)et之前,必須先set典阵,否則會(huì)報(bào)空指針異常奋渔。

4.如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法壮啊。For example嫉鲸,修改一下上面的例子:

 ThreadLocal<String> strLocal1 = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };

//    public void setValue() {
//        strLocal1.set(Thread.currentThread().getName());
//        strLocal1.set("dddddd");
//    }

    public String getStrLocal1() {
        return strLocal1.get();
    }

    public void Run() {
//        setValue();
        System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
        Thread thread = new Thread() {
            @Override
            public void run() {
                super.run();
//                setValue();
                System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
            }
        };
        thread.start();
        System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
    }

貼上Log:

<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4

沒有報(bào)錯(cuò)哦,趕緊動(dòng)手試試吧歹啼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玄渗,一起剝皮案震驚了整個(gè)濱河市座菠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藤树,老刑警劉巖浴滴,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岁钓,居然都是意外死亡升略,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門屡限,熙熙樓的掌柜王于貴愁眉苦臉地迎上來降宅,“玉大人,你說我怎么就攤上這事囚霸。” “怎么了激才?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵拓型,是天一觀的道長。 經(jīng)常有香客問我瘸恼,道長劣挫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任东帅,我火速辦了婚禮压固,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘靠闭。我一直安慰自己帐我,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布愧膀。 她就那樣靜靜地躺著拦键,像睡著了一般。 火紅的嫁衣襯著肌膚如雪檩淋。 梳的紋絲不亂的頭發(fā)上芬为,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音蟀悦,去河邊找鬼媚朦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛日戈,可吹牛的內(nèi)容都是我干的询张。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼浙炼,長吁一口氣:“原來是場噩夢啊……” “哼瑞侮!你這毒婦竟也來了的圆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤半火,失蹤者是張志新(化名)和其女友劉穎越妈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钮糖,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梅掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了店归。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阎抒。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖消痛,靈堂內(nèi)的尸體忽然破棺而出且叁,到底是詐尸還是另有隱情,我是刑警寧澤秩伞,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布逞带,位于F島的核電站,受9級特大地震影響纱新,放射性物質(zhì)發(fā)生泄漏展氓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一脸爱、第九天 我趴在偏房一處隱蔽的房頂上張望遇汞。 院中可真熱鬧,春花似錦簿废、人聲如沸空入。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽执庐。三九已至,卻和暖如春导梆,著一層夾襖步出監(jiān)牢的瞬間轨淌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工看尼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留递鹉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓藏斩,卻偏偏與公主長得像躏结,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子狰域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

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