ThreadLocal的增強版—InheritableThreadLocal源碼解析

  InheritableThreadLocal相比於ThreadLocal,支持子線程繼承父線程的數據,一起來看看它的實現原理吧!

1 從ThreadLocal說起

  在前面ThreadLocal文章中:ThreadLocal源碼深度解析與應用案例。我們知道每個線程都具有一個threadLocals屬性,用於存放線程本地變量。
  當然如果我們眼睛夠細,在我們threadLocals屬性下面還可以發現一個同類型的屬性inheritableThreadLocals,那這是變量又是幹什麼的呢?

    /* 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,他也對應着一個類:InheritableThreadLocal。
  在hreadLocal的文章的第一個案例的中,同一個ThreadLocal 變量在父線程(main)中被設置值後, 在子線程(child)中是獲取不到的。這應該是正常現象,因爲在子線程thread 裏面調用get 方法時當前線程爲thread 線程,而這裏調用set方法設置線程變量的是main 線程,兩者是不同的線程,自然子線程訪問時返回null。
  但是可能有些業務,需要子線程獲取到父線程的數據,那麼有沒有辦法讓子線程能訪問到父線程中的值? 答案是有,那就是InheritableThreadLocal。

2 InheritableThreadLocal的原理

2.1 InheritableThreadLocal的源碼

public class InheritableThreadLocal< T >
  extends ThreadLocal< T >

  InheritableThreadLocal繼承自ThreadLocal , 保存線程本地變量的集合也是使用的ThreadLocalMap。但是其增加了一個特性,就是讓子線程可以訪問在父線程中設置的本地變量。
  InheritableThreadLocal類的源碼很少,重寫了ThreadLocal的3個函數,也是該類的源全部源碼:

/**
 * 1
 * InheritableThreadLocal重寫的實現,當調用get/set 方法獲取當前線程內部的map 變量時,獲取
 * 的是inheritableThreadLocals 而不再是threadLocals。
 */
ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}

/**
 * ThreadLocal的實現
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
 * 操作InheritableThreadLocal對象時,第一次調用set/get方法時
 * 創建的是當前線程的inheritableThreadLocals 變量的實例而不再是threadLocals變量實例。
 */
void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

/**
 * ThreadLocal的實現
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

/**
 * 3
 * 該函數在父線程中創建子線程並向子線程複製inheritableThreadLocal變量時使用。
 * 在ThreadLocal中實現爲拋出UnsupportedOperationException異常
 */
protected T childValue(T parentValue) {
    return parentValue;
}

/**
 * ThreadLocal的實現
 */
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

2.2 傳遞數據的原理

  傳遞數據的原理還要從創建線程開始說起!實際上子線程的很多屬性如果自己沒有指定的話,都會繼承父線程的屬性,比如線程組、守護/用戶線程屬性、線程的上下文對象等。

/**
 * Thread類的構造器
 *
 * @param target 線程任務
 * @param name   線程名字
 */
public Thread(Runnable target, String name) {
    //內部調用init方法
    init(null, target, name, 0);
}

/**
 * Thread類的init方法,用於初始化線程的參數
 *
 * @param g         線程組
 * @param target    線程任務
 * @param name      線程名字
 * @param stackSize 當前線程棧大小
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    //調用另外一個init方法
    init(g, target, name, stackSize, null, true);
}

/**
 * Thread類的另一個init方法
 *
 * @param g                   線程組
 * @param target              線程任務
 * @param name                線程名字
 * @param stackSize           當前線程棧大小
 * @param acc                 訪問控制權限
 * @param inheritThreadLocals 如果爲true,則從構造線程(父線程)中繼承線程局部變量的初始值
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //設置名字
    this.name = name;
    //獲取當前線程,即父線程(main)
    Thread parent = currentThread();
    //如果inheritThreadLocals爲true並且父線程的inheritableThreadLocals 變量不爲null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //4 則將父線程inheritableThreadLocals的數據傳遞至子線程的inheritableThreadLocals屬性中
        //注意這裏時傳遞的引用值,對於引用類型的值(除了string)相當於淺拷貝,實際上他們還是同一個對象
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    //…………
}

/**
 * 4
 * ThreadLocal類的方法
 * 父線程的inheritableThreadLocals包裝到子線程的inheritableThreadLocals中
 *
 * @param parentMap 父線程的inheritableThreadLocals
 * @return ThreadLocalMap
 */
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    //5 新建ThreadLocalMap,包含指定的ThreadLocalMap的所有數據
    return new ThreadLocalMap(parentMap);
}

/**
 * ThreadLocalMap類的構造函數,包含指定的ThreadLocalMap的所有數據
 *
 * @param parentMap 指定的ThreadLocalMap對象
 */
private ThreadLocalMap(ThreadLocalMap parentMap) {
    //獲取table數組
    ThreadLocalMap.Entry[] parentTable = parentMap.table;
    //獲取長度
    int len = parentTable.length;
    //設置擴容閥值
    setThreshold(len);
    //新建table
    table = new ThreadLocalMap.Entry[len];

    for (int j = 0; j < len; j++) {
        ThreadLocalMap.Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //這裏終於調用到了重寫的childValue方法
                //用於把父線程的inheritableThreadLocals屬性的數據複製到新的ThreadLocalMap對象中
                Object value = key.childValue(e.value);
                ThreadLocalMap.Entry c = new ThreadLocalMap.Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

3 總結和案例

  InheritableThreadLocal 類重寫了三個方法。createMap將創建的ThreadLocalMap賦值給具體線程的inheritableThreadLocals變量。getMap將返回具體線程的inheritableThreadLocals變量,從而讓本地變量保存到了inheritableThreadLocals變量裏面,那麼線程在通過InheritableThreadLocal 類實例的set 或者get 方法設置變量時,就會創建當前線程的inheritableThreadLocals 變量。
  當在父線程中創建子線程時,構造函數會把父線程中inheritableThreadLocals變量裏面的本地變量複製一份(淺克隆)保存到子線程的inheritableThreadLocals 變量裏面。
  InheritableThreadLocal傳遞數據的案例:

public class InheritableThreadLocalTest {
    static InheritableThreadLocal<InheritableThreadLocalTest> threadLocal = new InheritableThreadLocal<>();

    public static void set() {
        threadLocal.set(new InheritableThreadLocalTest());
    }

    public static InheritableThreadLocalTest get() {
        return threadLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主線程中嘗試獲取值====>" + get());
        System.out.println("主線程中設置值====>");
        set();
        //主線程中嘗試獲取值
        System.out.println("主線程中再次嘗試獲取值====>" + get());
        System.out.println();

        System.out.println("開啓一條子線程====>");
        Thread thread = new Thread(new Th1(), "child");
        thread.start();
        //主線程等待子線程執行完畢
        thread.join();
        System.out.println();

        System.out.println("等待子線程執行完畢,主線程中再次嘗試獲取值====>" + get());
        System.out.println("獲取到的還是父線程原來的值,即父可以傳遞數據給子,子不能傳遞數據給父");
    }

    static class Th1 implements Runnable {
        @Override
        public void run() {
            System.out.println("子線程中嘗試獲取值====>" + get());
            System.out.println("能夠直接獲取到,說明父線程的數據傳遞給了子線程,並且獲取到的對象和父類獲取到的對象還是同一個,即淺拷貝");
            System.out.println("子線程中設置值====>");
            set();
            System.out.println("子線程中再次嘗試獲取值====>" + get());
            System.out.println("此時獲取到的,是自己設置的值");
            System.out.println();

        }
    }

    @Override
    public String toString() {
        return this.hashCode() + "";
    }

}

  結果:

主線程中嘗試獲取值====>null
主線程中設置值====>
主線程中再次嘗試獲取值====>1878246837

開啓一條子線程====>
子線程中嘗試獲取值====>1878246837
能夠直接獲取到,說明父線程的數據傳遞給了子線程,並且獲取到的對象和父類獲取到的對象還是同一個,即淺拷貝
子線程中設置值====>
子線程中再次嘗試獲取值====>185894025
此時獲取到的,是自己設置的值


等待子線程執行完畢,主線程中再次嘗試獲取值====>1878246837
獲取到的還是父線程原來的值,即父可以傳遞數據給子,子不能傳遞數據給父

  可以看出,子線程可以正常獲取到父線程本地變量的值。InheritableThreadLocal常常用在調用鏈路追蹤上。

如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!

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