【線程】InheritableThreadLocal 剖析 (十六)

我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼


一、前言

1.1 基礎

  1. 必須先看下【線程】ThreadLocal 剖析 (十四)
  2. 有時間再看下 【線程】ThreadLocal 內存泄漏問題(十五)

=== 點擊查看top目錄 ===

1.2 InheritableThreadLocal 能幹嘛?

  1. 能讓子線程讀取到父線程的變量。(可以用來監控 trace 方法鏈)

=== 點擊查看top目錄 ===

二、架構

2.1 代碼架構

在這裏插入圖片描述

2.2 正常初始化子進程

三、實戰 demo

  • 上圖可以理解下面所有demo

3.1 ThreadLocal 無法傳遞線程內變量

public class _22_01_TestInheritableThreadLocal {
    // 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> balance = new ThreadLocal();
//            InheritableThreadLocal.withInitial(() -> 100); // 假設初始賬戶有100塊錢


    public static void main(String[] args) throws Exception {
        balance.set(20);

        new Thread(new TaskDemo(), "A").start();

        // 讓子線程先走
        Thread.sleep(2000);

        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");
    }

    private static class TaskDemo implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " --> balance["
                    + balance.get() + "]");
        }
    }
}
  • 輸出:
A --> balance[null]
main --> balance[20]
  • 結論:ThreadLocal 無法傳遞線程內變量

3.2 實現數值的複製傳遞

  • 代碼:
public class _22_02_TestInheritableThreadLocal {

    // 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
//            InheritableThreadLocal.withInitial(() -> 100); // 假設初始賬戶有100塊錢


    public static void main(String[] args) throws Exception {
        balance.set(20);

        new Thread(new TaskDemo(), "A").start();

        // 讓子線程先走
        Thread.sleep(2000);

        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");
    }

    private static class TaskDemo implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " --> balance["
                    + balance.get() + "]");
        }
    }
}
  • 輸出:
A --> balance[20]
main --> balance[20]
  • 結論:父線程定義的線程內局部變量已經被子線程取到了。

=== 點擊查看top目錄 ===

3.3 變量在父線程的修改,不影響子線程(數值傳遞)

  • 代碼:
public class _22_03_TestInheritableThreadLocal {
    // 加個lock 控制流程順序
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    // 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> balance = new InheritableThreadLocal();

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

        // 1. 設置 balance
        balance.set(20);
        new TaskDemo().start();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);

        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");

        lock.lock();
        // 2. 修改 balance 的值,看下子線程的效果
        balance.set(40);
        System.out.println(Thread.currentThread().getName() + " set New balance ===");
        condition.signal();
        lock.unlock();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");
    }

    private static class TaskDemo extends Thread {
        public void run() {
            // 看下父線程的變量能否繼承襲來
            System.out.println(Thread.currentThread().getName() + " --> balance["
                    + balance.get() + "]");
            try {
                lock.lock();
                condition.await(); // 注意 await 本身會釋放 lock

                // 看下父線程的修改對子類的影響
                System.out.println(Thread.currentThread().getName() + " --> balance["
                        + balance.get() + "]");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
  • 輸出:
Thread-0 --> balance[20]
main --> balance[20]
main set New balance ===
Thread-0 --> balance[20]
main --> balance[40]

  • 結論:
  1. 看輸出父線程修改了變量的值之後,子線程仍然輸出了老值。
  2. 若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。詳看情景 3.9

3.4 變量在子線程的修改,不影響父線程(數值傳遞)

  • 這個情景跟上面 3.3 一樣,不做冗餘介紹。

=== 點擊查看top目錄 ===

3.5 變量在父線程的修改,不影響子線程(引用傳遞重新New)

  • 代碼
public class _22_04_TestInheritableThreadLocal {
    // 加個lock 控制流程順序
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    // 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Wallet> walletThreadLocal = new InheritableThreadLocal();

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

        // 1. 設置 balance
        walletThreadLocal.set(new Wallet(20));
//        walletThreadLocal.get().setBalance(40);
        new Thread(new TaskDemo(),"A").start();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);

        System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());

        lock.lock();
        // 2. 修改 balance 的值,看下子線程的效果
        walletThreadLocal.set(new Wallet(40));
        System.out.println(Thread.currentThread().getName() + " set New balance ===");
        condition.signal();
        lock.unlock();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
    }

    private static class TaskDemo implements Runnable {
        public void run() {
            // 看下父線程的變量能否繼承襲來
            System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
            try {
                lock.lock();
                condition.await(); // 注意 await 本身會釋放 lock

                // 看下父線程的修改對子類的影響
                System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    @Data
    @AllArgsConstructor
    private static class Wallet{
        private int balance;
        @Override
        public String toString(){
            return "Wallet.balance = [" + balance + "]";
        }

    }
}

  • 輸出:
AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [20]
mainWallet.balance = [40]
  • 結論:
  1. 父類指針指向的對象變了。子類的依舊沒變。所以改了跟他沒關係。

=== 點擊查看top目錄 ===

3.6 變量在子線程的修改,不影響父線程(引用傳遞重新New)

  • 同上 3.4

=== 點擊查看top目錄 ===

3.7 變量在父線程的修改,影響了子線程(改了指針指向的對象內的內容)

  • 情景 3.5 的代碼改一段,你可以看到效果
walletThreadLocal.set(new Wallet(40));

改成

walletThreadLocal.get().setBalance(40);

輸出:

AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [40]
mainWallet.balance = [40]
  • 結論:
  1. 數值已經被改了。
  2. 因爲父線程和子線程指向的是同一個對象。你把對象裏面的東西給改了。

=== 點擊查看top目錄 ===

3.8 變量在子線程的修改,影響了父線程(改了指針指向的對象內的內容)

  • 同上 3.7

=== 點擊查看top目錄 ===

3.9 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒作用。

  • 代碼
 // 3.9 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。
public class _22_06_TestInheritableThreadLocal {
    // 加個lock 控制流程順序
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    // 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> balance = new InheritableThreadLocal();

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

        // 1. 設置 balance
        new TaskDemo().start();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);

        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");

        lock.lock();
        // 2. 修改 balance 的值,看下子線程的效果
        balance.set(40);
        System.out.println(Thread.currentThread().getName() + " set New balance ===");
        condition.signal();
        lock.unlock();

        // sleep 讓子線程先走,看下效果
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + " --> balance["
                + balance.get() + "]");
    }

    private static class TaskDemo extends Thread {
        public void run() {
            // 看下父線程的變量能否繼承襲來
            System.out.println(Thread.currentThread().getName() + " --> balance["
                    + balance.get() + "]");
            try {
                lock.lock();
                condition.await(); // 注意 await 本身會釋放 lock

                // 看下父線程的修改對子類的影響
                System.out.println(Thread.currentThread().getName() + " --> balance["
                        + balance.get() + "]");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
  • 輸出:
Thread-0 --> balance[null]
main --> balance[null]
main set New balance ===
Thread-0 --> balance[null]
main --> balance[40]
  • 結論:
  1. 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。
  2. 這個實驗的目的和情景三 驗證的結論一致。

=== 點擊查看top目錄 ===

四、InheritableThreadLocal 源碼剖析

4.1 類 InheritableThreadLocal

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

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

}

類UML圖 可以得知:

  1. 重寫類父類 ThreadLocal 的兩個方法 getMap + createMap
  2. 實現了父類ThreadLocal的 childValue方法(父類的實現是空的)

=== 點擊查看top目錄 ===

4.2 getMap 方法 (getter)

  • java.lang.InheritableThreadLocal#getMap 方法
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
4.2.1 與重載之前的區別
  1. 重載之前的 ThreadLocal.getMap 方法
  2. 區別:ThreadLocal返回的是 t.threadLocals,重載後 InheritableThreadLocal 返回的是 t.inheritableThreadLocals。
4.2.2 查看上游
  • MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
    -
  • 總共有4個地方使用到了這個方法:
  1. java.lang.ThreadLocal#get 方法
  2. java.lang.ThreadLocal#setInitialValue 方法
  3. java.lang.ThreadLocal#set 方法
  4. java.lang.ThreadLocal#remove 方法

=== 點擊查看top目錄 ===

4.3 createMap 方法 (Setter)

  • java.lang.InheritableThreadLocal#createMap 方法
void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

=== 點擊查看top目錄 ===

4.3.1 ThreadLocalMap 構造方法
  • java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap) 方法
/**
 * 構建一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
 * 該函數只被 createInheritedMap() 調用.
 */
private ThreadLocalMap(ThreadLocalMap parentMap) {
    // 淺複製,讓子線程指向了原本父類內部的指向的 Entry表
    // 臨時表
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    // ThreadLocalMap 使用 Entry[] table 存儲ThreadLocal
    table = new Entry[len];

    //// 逐一複製 parentMap 的記錄
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        // e != null,說明不是髒數據。
        if (e != null) {
            @SuppressWarnings("unchecked")

            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                // 調用子線程複寫的 childValue 方法,處理 value,這個地方僅僅是淺複製
                Object value = key.childValue(e.value);
                // 生成新的 Entry
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

=== 點擊查看top目錄 ===

4.3.1.1 Object value = key.childValue(e.value);
  1. 絕不是網上有些人說的,只是爲了減輕閱讀代碼的難度。而是留給後續子類你可以靈活處理,比如 InheritableThreadLocal 只是簡單的淺複製,你完全可以繼續覆寫該方法改成深負責,或者你對數值進行處理,比如+1或者-1處理。或者可以把 traceId 加入前綴或者後綴

=== 點擊查看top目錄 ===

4.3.2 與重載之前的區別
  1. 重載之前的 ThreadLocal#createMap 方法
  2. 區別:ThreadLocal#createMap 是初始化 t.threadLocals ,重載後 InheritableThreadLocal#createMap 是初始化 t.inheritableThreadLocals 。

=== 點擊查看top目錄 ===

4.3.3 查看上游
  • MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
    在這裏插入圖片描述
  • 總共有2個地方使用到了這個方法:
  1. java.lang.ThreadLocal#setInitialValue 方法
  2. java.lang.ThreadLocal#set 方法

=== 點擊查看top目錄 ===

4.4 getMap 與 createMap 總結

  1. 一個是 getter ,一個是 setter
  2. override 之前操作的是 t.threadLocals ,之後是操作 t.inheritableThreadLocals (Thread中一個必定爲null)
  3. 看下 Thread.class 使你眼前一亮

=== 點擊查看top目錄 ===

4.5 Thread 類

public class Thread implements Runnable {
	...
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}

=== 點擊查看top目錄 ===

4.6 childValue 方法

4.6.2 ThreadLocal#createInheritedMap 方法
  • java.lang.ThreadLocal#createInheritedMap 方法
  • 創建 InheritableThreadLocal 的工廠方法
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
  • 繼續MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
    在這裏插入圖片描述

=== 點擊查看top目錄 ===

4.6.3 Thread#init 方法
  • java.lang.Thread#init 方法(注意:這個地方跑到了 Thread類來了)
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...

=== 點擊查看top目錄 ===

4.6.3.1 注意:parent.inheritableThreadLocals != null

注意: parent.inheritableThreadLocals != null ,這個前提是 父線程已經 set 了,也就是場景9強調的情況:變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒作用。

  • 繼續MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
  • 不斷查上去,是第一個
    在這裏插入圖片描述
  • java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

再上去,我們看到了是 Thread類的初始化。

=== 點擊查看top目錄 ===

4.6.4 Thread 初始化
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

=== 點擊查看top目錄 ===

五、番外篇

上一章節:【線程】ThreadGroup 實戰與剖析 (十七)
上一章節:【線程】ThreadLocal 內存泄漏問題(十五)

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