ThreadLocal

我們知道線程也是一個(gè)「對象」,當(dāng)線程這種對象想為我們提供一個(gè)「可以存取我們自定義變量的功能時(shí)」,來看下它是怎么做的。

一、Thread 中的成員變量ThreadLocalMap

  1. 這種功能通過成員變量java.lang.Thread#threadLocals來完成的,它是一個(gè)自定義Map類型:

    public class Thread implements Runnable {
        /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    

    可以看到它的:

    • null : 代表懶加載,只在你需要存數(shù)據(jù)時(shí)才會(huì)實(shí)例化一個(gè)ThreadLocalMap對象。
    • default : 訪問修飾符,代表只想讓java.lang下被訪問,就是給java.lang.ThreadLocal用。
  2. 為什么不在Thread里面給我們暴露方法?

    設(shè)計(jì)者覺得暴露一個(gè)類似get()/put()方法給你,讓你自己用任意的Object類型K/V操作太Low。

    所以他把這個(gè)功能「封裝」為一個(gè)類,稱為線程本地變量java.lang.ThreadLocal,每一個(gè)你需要的變量就是一個(gè)此類的實(shí)例。

二、ThreadLocal 實(shí)現(xiàn)

它們的關(guān)系是,Thread has a ThreadLocalMap has a Entry has a ThreadLocal。

  1. 既然用Map存,那KeyValue分別是什么?

    • Key : 線程變量對象ThreadLocal。
    • Value : 你自定義操作的那個(gè)數(shù)據(jù)Object
  2. 當(dāng)然,這個(gè)內(nèi)部Map設(shè)計(jì)意圖肯定要對使用者透明

    所以我們不能直接操作這個(gè)Map,而是使用的ThreadLocal的三個(gè)public方法:get()/set()/remove()

    set 方法

    public void set(T value) {
        // 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 簡單的返回Thread 對象的成員變量threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    get 方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 這個(gè)Map 沒有g(shù)et方法只有g(shù)etEntry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                // 你之前設(shè)置的值
                return (T)e.value;
        }
        return setInitialValue();
    }
    

    remove 方法

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    

三、ThreaLocal 使用

  1. Example

    JavaDoc 中已經(jīng)給出了,摘抄一下:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ThreadId {
        // Atomic integer containing the next thread ID to be assigned
        private static final AtomicInteger nextId = new AtomicInteger(0);
    
        // Thread local variable containing each thread's ID
        private static final ThreadLocal<Integer> threadId =
            new ThreadLocal<Integer>() {
                @Override protected Integer initialValue() {
                    return nextId.getAndIncrement();
            }
        };
    
        // Returns the current thread's unique ID, assigning it if necessary
        public static int get() {
            return threadId.get();
        }
    }
    
  2. 解釋:初始化和set()方法

    首先,之所以要使用「匿名內(nèi)部類」及其對象,是因?yàn)橄胱屇阍谝恍写a內(nèi)就完成初始化。除此之外,initialValue()set()的實(shí)現(xiàn)代碼區(qū)別不大。

    然后,一旦哪個(gè)Thread引用到這個(gè)ThreadLocal對象了,就會(huì)在自己的threadLocals中被設(shè)置一個(gè)值。

  3. 獲取不必解釋,就是從當(dāng)前線程的Map中取即可。

四、WeakReference 的應(yīng)用

1. 假設(shè)我們來設(shè)計(jì)那個(gè)Map和其中的Entry

  1. Map 和Entry

    static class ThreadLocalMap {
        
            static class Entry<K,V> {
            final K key;
            V value;
        }
    }    
    

  2. 我們的使用場景是這樣的

    // 這里注意,沒有final 了
    private static ThreadLocal<Integer> threadId =
        new ThreadLocal<Integer>() {
            @Override protected Integer initialValue() {
                return nextId.getAndIncrement();
        }
    }
    

    當(dāng)我們想不再使用threadId時(shí),我們會(huì)把threadId設(shè)置為null

    public void method1(){
        // ... 一些操作
        threadId = null;
    }
    
  3. 此時(shí),GC 會(huì)清除這個(gè)對象嗎?

    分析之后我們發(fā)現(xiàn)其實(shí)大概率不會(huì),因?yàn)槟莻€(gè)ThreadLocal對象并非「不可達(dá)對象」,這是因?yàn)槲覀兊?code>Thread.threadLocals.Entry.key仍然引用著它。只有當(dāng)Thread對象本身被銷毀后它才會(huì)被回收,不過在Web 項(xiàng)目或RPC 項(xiàng)目中我們一般都使用線程池,所以一般來說線程很難被銷毀。

    很明顯,我們的設(shè)計(jì)有問題。那我們看看設(shè)計(jì)者是如何做的。

2. 實(shí)際代碼

  1. 來看看實(shí)際代碼

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

    Entry是個(gè)弱引用。這意味著:一旦GC 確定ThreadLocal對象是「弱可達(dá)對象」時(shí),它將清除所有引用此對象的「弱引用」,也就是說此時(shí)Entry也將被清除。

  2. 最后再來復(fù)習(xí)一下對象的「可到達(dá)性」

    從最強(qiáng)到最弱,不同的可到達(dá)性級別反映了對象的生命周期。在操作上,可將它們定義如下:

    1. 強(qiáng)可到達(dá) 對象:如果某一線程可以不必遍歷所有引用對象而直接到達(dá)一個(gè)對象,則該對象是強(qiáng)可到達(dá) 對象。新創(chuàng)建的對象對于創(chuàng)建它的線程而言是強(qiáng)可到達(dá)對象。
    2. 軟可到達(dá) 對象:如果一個(gè)對象不是強(qiáng)可到達(dá)對象,但通過遍歷某一軟引用可以到達(dá)它,則該對象是軟可到達(dá) 對象。
    3. 弱可到達(dá) 對象:如果一個(gè)對象既不是強(qiáng)可到達(dá)對象,也不是軟可到達(dá)對象,但通過遍歷弱引用可以到達(dá)它,則該對象是弱可到達(dá) 對象。當(dāng)清除對某一弱可到達(dá)對象的弱引用時(shí),便可以終止此對象了。
    4. 虛可到達(dá) 對象:如果一個(gè)對象既不是強(qiáng)可到達(dá)對象,也不是軟可到達(dá)對象或弱可到達(dá)對象,它已經(jīng)終止,并且某個(gè)虛引用在引用它,則該對象是虛可到達(dá) 對象。

    最后,當(dāng)不能以上述任何方法到達(dá)某一對象時(shí),該對象是不可到達(dá) 對象,因此可以回收此對象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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