Java中的ThreadLocal和 InheritableThreadLocal

作者: 一字馬胡
轉載標志 【2017-11-03】

更新日志

日期 更新內容 備注
2017-11-03 添加轉載標志 持續更新

ThreadLocal

ThreadLocal從字面理解就是線程本地變量,貌似是一種線程私有的緩存變量的容器。為了說明ThreadLocal的特點,舉個例子:比如有三個人,每個人比作一個線程,它們都需要一個袋子來裝撿到的東西,也就是每個線程都希望自己有一個容器,當然,自己的撿到的東西肯定不希望和別人分享啊,也就是希望這個容器對其他人(線程)是不可見的,如果現在只有一個袋子,那怎么辦?

  1. 每個人在撿東西之前一定會先搶到那個唯一的袋子,然后再撿東西,如果使用袋子的時間到了,就會馬上把里面的東西消費掉,然后把袋子放到原來的地方,然后再次去搶袋子。這個方案是使用鎖來避免線程競爭問題的,三個線程需要競爭同一個共享變量。
  2. 我們假設現在不是只有一個袋子了,而是有三個袋子,那么就可以給每個人安排一個袋子,然后每個人的袋子里面的對象是對其他人不可見的,這樣的好處是解決了多個人競爭同一個袋子的問題。這個方案就是使用ThreadLocal來避免不必要的線程競爭的。

大概了解了ThreadLocal,下面來看看它的使用方法:


    private static class UnsafeThreadClass {

        private int i;

        UnsafeThreadClass(int i) {
            this.i = i;
        }

        int getAndIncrement() {
            return ++ i;
        }

        @Override
        public String toString() {
            return "[" + Thread.currentThread().getName() + "]" + i;
        }
    }
    
 private static ThreadLocal<UnsafeThreadClass> threadLocal = new ThreadLocal<>();

    static class ThreadLocalRunner extends Thread {
        @Override
        public void run() {

            UnsafeThreadClass unsafeThreadClass = threadLocal.get();

            if (unsafeThreadClass == null) {
                unsafeThreadClass = new UnsafeThreadClass(0);
                threadLocal.set(unsafeThreadClass);
            }

            unsafeThreadClass.getAndIncrement();

            System.out.println(unsafeThreadClass);
        }

    }

上面的例子僅僅是為了說明ThreadLocal可以為每個線程保存一個本地變量,這個變量不會受到其他線程的干擾,你可以使用多個ThreadLocal來讓線程保存多個變量,下面我們分析一下ThreadLocal的具體實現細節,首先展示了ThreadLocal提供的一些方法,我們重點關注的是get、set、remove方法。

ThreadLocal方法

首先,我們需要new一個ThreadLocal對象,那么ThreadLocal的構造函數做了什么呢?


    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

很遺憾它什么都沒做,那么初始化的過程勢必是在首次set的時候做的,我們來看一下set方法的細節:


    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

看起來首先根據當前線程獲取到了一個ThreadLocalMap,getMap方法是做了什么?


    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

非常的簡潔,是和Thread與生俱來的,我們看一下Thread中的相關定義:


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

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

關于inheritableThreadLocals將在下一小節再學習總結。

獲得了線程的ThreadLocalMap之后,如果不為null,說明不是首次set,直接set就可以了,注意key是this,也就是當前的ThreadLocal啊不是Thread。如果為空呢?說明還沒有初始化,那么就需要執行createMap這個方法:


    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

沒什么特別的,就是初始化線程的threadLocals,然后設定key-value。

下面分析一下get的邏輯:


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

和set一樣,首先根據當前線程獲取ThreadLocalMap,然后判斷是否為null,如果為null,說明ThreadLocalMap還沒有被初始化啊,那么就返回方法setInitialValue的結果,這個方法做了什么?


    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;
    }
    
    protected T initialValue() {
        return null;
    }

最后會返回null,但是會做一些初始化的工作,和set一樣。在get里面,如果返回的ThreadLocalMap不為null,則說明ThreadLocalMap已經被初始化了,那么就可以正常根據ThreadLocal作為key獲取了。

當線程退出時,會清理ThreadLocal,可以看下面的代碼:


    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

這里做了大量“Help GC”的工作。包括我們本節所講的threadLocals和下一小節要講的inheritableThreadLocals都會被清理。

如果我們想要顯示的清理ThreadLocal,可以使用remove方法:


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

邏輯較為直接,很好理解。

InheritableThreadLocal

ThreadLocal固然很好,但是子線程并不能取到父線程的ThreadLocal的變量,比如下面的代碼:


    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
    private static InheritableThreadLocal<Integer> inheritableThreadLocal =
            new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        integerThreadLocal.set(1001); // father
        inheritableThreadLocal.set(1002); // father

        new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
                + integerThreadLocal.get() + "/"
                + inheritableThreadLocal.get())).start();

    }

//output:
Thread-0:null/1002

使用ThreadLocal不能繼承父線程的ThreadLocal的內容,而使用InheritableThreadLocal時可以做到的,這就可以很好的在父子線程之間傳遞數據了。下面我們分析一下InheritableThreadLocal的實現細節,下面展示了InheritableThreadLocal提供的方法:

InheritableThreadLocal方法

InheritableThreadLocal繼承了ThreadLocal,然后重寫了上面三個方法,所以除了上面三個方法之外,其他所有對InheritableThreadLocal的調用都是對ThreadLocal的調用,沒有什么特別的。我們上文中提到了Thread類,里面有我們本文關心的兩個成員,我們來看一下再Thread中做了哪些工作,我們跟蹤一下new一個Thread的調用路徑:


new Thread()

init(ThreadGroup g, Runnable target, String name, long stackSize)
                       

init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)

-> 
           if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

createInheritedMap(ThreadLocalMap parentMap)


ThreadLocalMap(ThreadLocalMap parentMap) 

上面列出了最為關鍵的代碼,可以看到,最后會調用ThreadLocal的createInheritedMap方法,而該方法會新建一個ThreadLocalMap,看一下構造函數的內容:


        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

parentMap就是父線程的ThreadLocalMap,這個構造函數的意思大概就是將父線程的ThreadLocalMap復制到自己的ThreadLocalMap里面來,這樣我們就可以使用InheritableThreadLocal訪問到父線程中的變量了。

對ThreadLocal更為具體和深入的分析將在其他的篇章中進行,本文點到即可,為了深入理解ThreadLocal,可以閱讀ThreadLocalMap的源碼,以及可以在項目中多思考是否可以使用ThreadLocal來做一些事情,比如,如果我們具有這樣一種線程模型,一個任務從始至終只會被一個線程執行,那么可以使用ThreadLocal來計算運行該任務的時間。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容