InheritableThreadLocal父子線程變量共享實現原理


概述

  我們知道ThreadLocal 的設計初衷是為了解決多線程並發導致的線程安全問題,向每一個線程提供一個自己的變量副本,實現變量隔離。那如何在不同線程之間共享變量呢?InheritableThreadLocal為解決此問題而生,使用她可以實現父子線程訪問ThreadLocal的值。
  實現變量傳遞的關鍵是在類Thread中定義的本地變量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;

案例分析

  下面先提供一個案例,說明InheritableThreadLocal可以實現父子線程間傳遞變量。

/**
 * 線程間共享變量
 *
 * @author Wiener
 * @date 2020/10/27
 */
public class ShareArgsProblem {
    private static void shareArgs() {
        //主線程中賦值
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        final ThreadLocal itl = new InheritableThreadLocal();
        itl.set("帥得一匹!");
        threadLocal.set("在 ThreadLocal 中存放了值");
        System.out.println(String.format("① 當前線程名稱:%s", Thread.currentThread().getName()));
        Thread t = new Thread() {
            @Override
            public void run() {
                super.run();
                //子線程中讀取
                System.out.println(String.format("樓蘭的胡楊帥么?%s", itl.get()));
                System.out.println(String.format("拿不到ThreadLocal 存儲的值:%s", threadLocal.get()));
                System.out.println(String.format("② 當前線程名稱: %s", Thread.currentThread().getName()));
            }
        };
        t.start();
    }

    public static void main(String[] args) {
        shareArgs();
    }
}

  執行main函數,輸出結果如下:

  在上面的測試用例中,開啟兩個線程——主線程和子線程t,主線程中創建了兩個變量和一個新的線程t,並嘗試在線程t中獲取本地變量的值。從輸出結果可以看得出來,我們拿不到ThreadLocal類型變量 threadLocal 的值,但是,能夠准確拿到 InheritableThreadLocal 類型的變量 itl的值。下面分析為什么可以讀取出來InheritableThreadLocal 類型的變量的值。

InheritableThreadLocal共享變量原理分析

  先讓我們看看InheritableThreadLocal的寥寥數行源碼:


/**
 * This class extends {@code ThreadLocal} 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 {@code childValue}
 * method in this class.
 *
 * <p>Inheritable thread-local variables are used in preference to
 * ordinary thread-local variables when the per-thread-attribute being
 * maintained in the variable (e.g., User ID, Transaction ID) must be
 * automatically transmitted to any child threads that are created.
 * @author  Josh Bloch and Doug Lea
 */

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

  類InheritableThreadLocal繼承自 ThreadLocal,作為子類,它重寫了如上所述的三個方法。從上述注釋得知,父線程thread-local 變量會被傳遞至子線程中,下面看看如何傳遞的。


    public Thread() {//創建新線程時調用的構造方法
        this(null, null, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        this(group, target, name, stackSize, null, true);
    }
    /**
     * Initializes a Thread.
     */
    private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
		 ...
        Thread parent = currentThread();
		 ...
        setPriority(priority);
        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 */
        this.tid = nextThreadID();
    }

  跟着進去幾層后,發現在①處給inheritableThreadLocals賦值,繼續看 ThreadLocal.createInheritedMap的返回值是什么:

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
    /**
     * Construct a new map including all Inheritable ThreadLocals
     * from given parent map. Called only by createInheritedMap.
     *
     * @param parentMap the map associated with parent thread.
     */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table; //父線程的table
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];//當前線程的table
        //循環取父線程的值到當前線程
        for (Entry e : parentTable) { 
            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++;
                }
            }
        }
    }

  從構造方法ThreadLocalMap可以得知,首先拿到父線程中的鍵值對表table,然后循環遍歷parentTable,把父線程中的這些值通過值傳遞復制到子線程的ThreadLocalMap對象中,此時子線程的成員變量 inheritableThreadLocals中就有了值。其實,ThreadLocalMap的注釋已經明確告訴我們,這里將把父線程的可遺傳本地變量(Inheritable ThreadLocals)復制到當前線程中。這種模式,有點類似於ClassLoader的 loadClass() 的機制。
  通過查看Object value = key.childValue(e.value)的實現方式,我們知道這里是值傳遞。

小結

  Thread對象通過成員變量ThreadLocal.ThreadLocalMap inheritableThreadLocals維護從父線程(創建該線程的線程)繼承而來的數據。原理就是在父線程創建子線程時,如果父線程的inheritableThreadLocals不是null,則ThreadLocalMap中的構造函數會復制一份保存到子線程的inheritableThreadLocals變量中。
  由於本地變量在父子傳遞過程中通過值傳遞,所以即使父線程的本地變量發生了改變,子線程的本地變量依舊是創建線程時初始化的值。在實際的應用場景里,基本都是使用線程池來進行多線程編程,因線程池復用線程,而非每次創建新的線程,所以如果更新父線程InheritableThreadLocal 的值,被復用的子線程中的InheritableThreadLocal變量值不會被更新,從而導致數據異常。阿里開源的TransmittableThreadLocal 可以解決此問題,感興趣的老鐵們可以去了解一下。
  以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多點贊支持。

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM