ThreadLocal的進化——InheritableThreadLocal

以前有介紹過 ThreadLocal,JDK 後來針對此作了一個升級版本 InheritableThreadLocal,今天就來好好介紹下。
java

爲何要升級

首先咱們來想一想,爲何要升級?這就要提及 ThreadLocal 的功能了。git

咱們知道,ThreadLocal 設計初衷是爲了在多線程環境下,針對每個線程能有一個本身的副本,這樣能夠在必定程度上解決多線程併發修改的問題。可是,咱們能夠在此基礎上作一個拓展,好比context,咱們能夠利用 ThreadLocal 針對每個線程都有一個本身的上下文,通常都是寫成ThreadLocal<Context>,這樣在這個線程上作的全部修改均可以被你們利用到。github

此時設想一下,假如咱們新建一個子線程,那這個子線程能夠獲取到父線程的context嗎?理論上但願能夠達成這樣的效果,實際上呢?讓咱們看看:多線程

public class ThreadLocalContext {

    private static ThreadLocal<Context> context = new ThreadLocal<>();

    static class Context {

        String name;

        int value;
    }

    public static void main(String[] args) {
        Context context = new Context();
        context.name = "mainName";
        context.value = 10;
        ThreadLocalContext.context.set(context);

        Thread childThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Context childContext = ThreadLocalContext.context.get();
                        System.out.println(childContext.name);
                        System.out.println(childContext.value);
                    }
                }
        );
        childThread.start();
    }
}

運行 main 方法以後,直接在子線程中拋錯,這樣確實符合咱們的預期,但若是咱們想達到子線程能夠獲取到父線程的 context這樣的效果該如何作呢?併發

首先想到的就是在生成子線程的時候,將父線程 ThreadLocal 裏的值傳給子線程。這樣作雖然能達到效果,但過程比較繁雜,且代碼侵入性強。ide

這個時候就能夠用InheritableThreadLocal了。學習

什麼是 InheritableThreadLocal

看源碼

先讓咱們看看它的源碼,你們不要怕,它的源碼不多:this

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

首先它繼承自 ThreadLocal,那麼它其實就是 ThreadLocal 的一個拓展版本,接下來就是這三個方法,其實這三個方法在 ThreadLocal 都是有的,咱們來看看:線程

T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

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

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

除了childValue方法在 ThreadLocal 中是拋出異常的,其他兩個方法在兩個類中都幾乎是同樣,只是針對的對象不一樣而已,但threadLocalsinheritableThreadLocals都是ThreadLocal.ThreadLocalMap類型,這個在以前的文章中有說過,就是一個 key 爲弱引用的 Entry,這個倒不是重點。設計

咱們再來看看 inheritableThreadLocals 是在什麼時候被初始化的,從源碼能夠得知:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
                // 省略無關代碼
                ...
                Thread parent = currentThread();
                ...
                // 省略無關代碼
                ...
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
                ...
        }

當咱們經過父線程調用 Thread 的構造方法生成一個子線程時,其構造方法最終會調用這個 init 方法。從這兒能夠看出, inheritableThreadLocals 是來自於父線程的 inheritableThreadLocals,那這樣也就解釋了爲何 inheritableThreadLocals 支持在子線程中使用父線程中存儲的變量。

如何使用

讓咱們仍是回到上文提到的 context 的例子,用 InheritableThreadLocal 進行改造:

public class ThreadLocalContext {

    private static InheritableThreadLocal<Context> context = new InheritableThreadLocal<>();

    static class Context {

        String name;

        int value;
    }

    public static void main(String[] args) {
        Context context = new Context();
        context.name = "mainName";
        context.value = 10;
        ThreadLocalContext.context.set(context);

        Thread childThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Context childContext = ThreadLocalContext.context.get();
                        System.out.println(childContext.name);
                        System.out.println(childContext.value);
                    }
                }
        );
        childThread.start();
    }
}

運行後,不只沒有拋出異常,並且在子線程中輸出了父線程設置好的值。皆大歡喜!

總結

今天分享了 InheritableThreadLocal,主要是由於週三在攜程的分享會上聽到了別人談了這方面的分享,主講人講了一個更加廣泛的問題,若是咱們用線程池提交任務的話,線程池中的線程在執行任務時,如何可以得到提交任務的線程的 context,這時就要用到阿里的開源組件 TTL,我會在以後進行介紹。

加入攜程也有1個月了,雖然感覺到大公司有很多的弊端,好比溝通難等,但也有很多的優勢,好比技術分享會,雖然也是忙裏偷閒去參加的,但有了更多和技術相關的能夠學習和交流的機會,也挺好的。

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/

公衆號:健程之道

相關文章
相關標籤/搜索