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存儲的值,在每個(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就是用來存儲實(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)建的副本,存儲在每個(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容