1. 程式人生 > >Java 多執行緒:InheritableThreadLocal 實現原理

Java 多執行緒:InheritableThreadLocal 實現原理

轉載自https://github.com/pzxwhc/MineKnowContainer/issues/20

前言

介紹 InheritableThreadLocal 之前,假設對 ThreadLocal 已經有了一定的理解,比如基本概念,原理,如果沒有,可以參考:Java 多執行緒:threadlocal關鍵字

這裡再複習下 ThreadLocal 的原理,因為會對 InheritableThreadLocal 的理解 有重大的幫助:

  1. 每個執行緒都有一個 ThreadLocalMap 型別的 threadLocals 屬性。
  2. ThreadLocalMap 類相當於一個Map,key 是 ThreadLocal 本身,value 就是我們的值。
  3. 當我們通過 threadLocal.set(new Integer(123)); ,我們就會在這個執行緒中的 threadLocals 屬性中放入一個鍵值對,key 是 這個 threadLocal.set(new Integer(123)); 的 threadlocal,value 就是值。
  4. 當我們通過 threadlocal.get() 方法的時候,首先會根據這個執行緒得到這個執行緒的 threadLocals 屬性,然後由於這個屬性放的是鍵值對,我們就可以根據鍵 threadlocal 拿到值。 注意,這時候這個鍵 threadlocal 和 我們 set 方法的時候的那個鍵 threadlocal 是一樣的,所以我們能夠拿到相同的值。

Ps:如果這個原理沒搞清楚,那麼下文估計有比較難理解,所以建議完完全全搞懂這個原理。

InheritableThreadLocal 概念

從上面的介紹我們可以知道,我們其實是根據 Thread.currentThread(),拿到該執行緒的 threadlocals,從而進一步得到我們之前預先 set 好的值。那麼如果我們新開一個執行緒,這個時候,由於 Thread.currentThread() 已經變了,從而導致獲得的 threadlocals 不一樣,我們之前並沒有在這個新的執行緒的 threadlocals 中放入值,那麼我就再通過 threadlocal.get()方法 是不可能拿到值的。例如如下程式碼:

public class Test {

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    public static void main(String args[]){
        threadLocal.set(new Integer(123));

        Thread thread = new MyThread();
        thread.start();

        System.out.println("main = " + threadLocal.get());
    }

    static class MyThread extends Thread{
        @Override
        public void run(){
            System.out.println("MyThread = " + threadLocal.get());
        }
    }
}

輸出是:

main = 123
MyThread = null

那麼這個時候怎麼解決? InheritableThreadLocal 就可以解決這個問題。 先看一個官方對它的介紹:

 * This class extends <tt>ThreadLocal</tt> to provide inheritance of values
 * from parent thread to child thread: when a child thread is created, the
 * child receives initial values for all inheritable thread-local variables
 * for which the parent has values.  Normally the child's values will be
 * identical to the parent's; however, the child's value can be made an
 * arbitrary function of the parent's by overriding the <tt>childValue</tt>
 * method in this class.

也就是說,我們把上面的

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

改成

public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

再執行,就會有結果:

main = 123
MyThread = 123

也就是子執行緒或者說新開的執行緒拿到了該值。那麼,這個究竟是怎麼實現的呢,key 都變了,為什麼還可以拿到呢?

InheritableThreadLocal 原理

我們可以首先可以瀏覽下 InheritableThreadLocal 類中有什麼東西:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

其實就是重寫了3個方法。

首先,當我們呼叫 get 方法的時候,由於子類沒有重寫,所以我們呼叫了父類的 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();
}

這裡會有一個Thread.currentThread() , getMap(t) 方法,所以就會得到這個執行緒 threadlocals。 但是,由於子類 InheritableThreadLocal 重寫了 getMap()方法,再看上述程式碼,我們可以看到:
其實不是得到 threadlocals,而是得到 inheritableThreadLocals。 inheritableThreadLocals 之前一直沒提及過,其實它也是 Thread 類的一個 ThreadLocalMap 型別的 屬性,如下 Thread 類的部分程式碼:

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

那麼,這裡看 InheritableThreadLocal 重寫的方法,感覺 inheritableThreadLocals 和 threadLocals 幾乎是一模一樣的作用,只是換了個名字而且,那麼究竟 為什麼在新的 執行緒中 通過 threadlocal.get() 方法還能得到值呢?

這時候要注意 childValue 方法,我們可以看下它的官方說明:

 * Computes the child's initial value for this inheritable thread-local
 * variable as a function of the parent's value at the time the child
 * thread is created.  This method is called from within the parent
 * thread before the child is started.

這個時候,你明白了,是不是在 建立執行緒的時候做了手腳,做了一些值的傳遞,或者這裡利用上了 inheritableThreadLocals 之類的。

其實,是的:

  • 關鍵在於 Thread thread = new MyThread();
  • 關鍵在於 Thread thread = new MyThread();
  • 關鍵在於 Thread thread = new MyThread();

這不是一個簡簡單單的 new 操作。當我們 new 一個 執行緒的時候:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

然後:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
    init(g, target, name, stackSize, null);
}

然後:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
     ......
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
    ......
    }

這時候有一句 'ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);' ,然後

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

繼續跟蹤:

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++;
                }
            }
        }
    }

當我們建立一個新的執行緒的時候X,X執行緒就會有 ThreadLocalMap 型別的 inheritableThreadLocals ,因為它是 Thread 類的一個屬性。

然後

先得到當前執行緒儲存的這些值,例如 Entry[] parentTable = parentMap.table; 。再通過一個 for 迴圈,不斷的把當前執行緒的這些值複製到我們新建立的執行緒X 的inheritableThreadLocals 中。就這樣,就ok了。

那麼這樣會有一個什麼結果呢?

結果就是我們建立的新執行緒X 的inheritableThreadLocals 變數中已經有了值了。那麼我在新的執行緒X中呼叫threadlocal.get() 方法,首先會得到新執行緒X 的 inheritableThreadLocals,然後,再根據threadlocal.get()中的 threadlocal,就能夠得到這個值。

這樣就避免了 新執行緒中得到的 threadlocals 沒有東西。之前就是因為沒有東西,所以才拿不到值。

所以說 整個 InheritableThreadLocal 的實現原理就是這樣的。

總結

  1. 首先要理解 為什麼 在 新執行緒中得不到值,是因為我們其實是根據 Thread.currentThread(),拿到該執行緒的 threadlocals,從而進一步得到我們之前預先 set 好的值。那麼如果我們新開一個執行緒,這個時候,由於 Thread.currentThread() 已經變了,從而導致獲得的 threadlocals 不一樣,我們之前並沒有在這個新的執行緒的 threadlocals 中放入值,那麼我就再通過 threadlocal.get()方法 是不可能拿到值的。
  2. 那麼解決辦法就是 我們在新執行緒中,要把父執行緒的 threadlocals 的值 給複製到 新執行緒中的 threadlocals 中來。這樣,我們在新執行緒中得到的 threadlocals 才會有東西,再通過 threadlocal.get() 中的 threadlocal,就會得到值。

參考