ThreadLocal 系列之 InheritableThreadLocal

相關文章:

ThreadLocal 存在的問題

上一篇文章中簡單介紹了什麼是 ThreadLocal,以及爲什麼我們可以根據它去操作線程級別的變量。但是 ThreadLocal 嚴格綁定當前線程,不適用於線程切換的情況,比如在分佈式 Tracing 中,你無法控制會不會有服務存在線程切換。先看一個例子:

public class InheritableThreadLocalTest1 {

    private static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();

    public static void main(String[] args) {
        HOLDER.set(1);
        System.out.println(currentName()+":"+HOLDER.get());
        new Thread(()-> System.out.println(currentName()+":"+HOLDER.get())).start();
    }

    private static String currentName(){
        return Thread.currentThread().getName();
    }
}

輸出結果:

main:1
Thread-0:null

可以發現,在子線程(即 Thread-0 線程)中無法獲取父線程(即 main 線程)的數據,用官方的翻譯來說的話就是 ThreadLocal 不支持繼承。這個也很好理解,雖然都是 Thread 類的實例,但是不同實例之間怎麼可能直接獲取對方成員變量的數據呢。

一種解決思路

一般我們多線程操作都是會比如實現 Runable,封裝一個 Task 類,我們可以提前在 Task 類中傳入父線程的變量,示例代碼如下:

/**
 * @author 
 * @Description 執行任務包裝類
 * @Date 創建於 2020-03-29 14:51
 */
public class MyTask implements Runnable {

    private Runnable runnable;

    private Integer inheritableValue;

    @Override
    public void run() {
        try {
            InheritableThreadLocalTest.HOLDER.set(inheritableValue);
            runnable.run();
        }finally {
            InheritableThreadLocalTest.HOLDER.remove();
        }
    }

    public MyTask(Runnable runnable) {
        this.runnable = runnable;
        this.inheritableValue =  InheritableThreadLocalTest.HOLDER.get();
    }
}

測試代碼:

public class InheritableThreadLocalTest {

    public static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();

    public static void main(String[] args) {
        HOLDER.set(1);
        System.out.println(currentName() + ":" + HOLDER.get());
        Runnable task = ()-> System.out.println(currentName()+":"+HOLDER.get());
        new Thread(task,"simple task").start();

        new Thread(new MyTask(task),"inheritable task").start();
    }

    private static String currentName() {
        return Thread.currentThread().getName();
    }
}

輸出結果:

main:1
simple task:null
inheritable task:1

會發現是可以獲取到父線程的值。JDK 提供了 InheritableThreadLocal 解決 ThreadLocal 繼承的問題。

InheritableThreadLocal

InheritableThreadLocal 繼承了 ThreadLocal,有這麼幾個方法:
在這裏插入圖片描述

結合上一篇文章中分析的 ThreadLocal#set 方法,優先看下 createMap 方法:

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

可以發現與 ThreadLocal 不同的是,InheritableThreadLocal 關注的是 ThreadinheritableThreadLocals 變量:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

也是 ThreadLocalMap 類型,咋一看並沒有什麼特殊的,參考上文我們自己實現的一種解決思路,我們是在子線程執行方法前將父線程的值設置進去的,看看 JDK 是怎麼做的,先看 Thread#init 方法:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        this.name = name;
				//獲取當前線程
        Thread parent = currentThread();
        ...
  			//inheritThreadLocals 一般情況都是 true
  			//如果父線程有可繼承的變量,則賦值給當前線程
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

後面的 createInheritedMap 方法的細節這裏就不過多贅述了,主要積就是將父線程中所有可繼承的變量賦值給子線程。可以發現,思路都是一致的,就是提前預存父線程中的數據。

歡迎關注公衆號
​​​​​​在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章