詳解InheritableThreadLocal類原理

  • 2019 年 10 月 3 日
  • 筆記

在Java並發編程中,InheritableThreadLocal 與 ThreadLocal 都可以用於執行緒間通訊,不同的是 InheritableThreadLocal 繼承了 ThreadLocal,並且擴展了 ThreadLocal。使用類 InheritableThreadLocal 可使子執行緒繼承父執行緒的值。相反,類 ThreadLocal 不能實現值繼承。

使用示例:

public class LocalThread extends Thread {      private static InheritableThreadLocal local = new InheritableThreadLocal();        @Override      public void run() {          System.out.println("thread執行緒:"+ local.get());      }        public static void main(String[] args) throws InterruptedException {          local.set("main的值");          LocalThread t = new LocalThread();          t.start();          System.out.println("main執行緒:"+ local.get());      }    }

分析下 InheritableThreadLocal 類源碼:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {      /**       * Computes the child's initial value for this inheritable thread-local       * variable as a function of the parent's value at the time the child       * thread is created.  This method is called from within the parent       * thread before the child is started.       * <p>       * This method merely returns its input argument, and should be overridden       * if a different behavior is desired.       *       * @param parentValue the parent thread's value       * @return the child thread's initial value       */      protected T childValue(T parentValue) {          return parentValue;      }        /**       * Get the map associated with a ThreadLocal.       *       * @param t the current thread       */      ThreadLocalMap getMap(Thread t) {         return t.inheritableThreadLocals;      }        /**       * Create the map associated with a ThreadLocal.       *       * @param t the current thread       * @param firstValue value for the initial entry of the table.       */      void createMap(Thread t, T firstValue) {          t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);      }  }

可以看到,getMap() 方法和 creatMap() 方法都是重寫的 ThreadLocal 類方法,區別在於把 ThreadLocal 中的 threadLocals 換成了 inheritableThreadLocals,這兩個變數都是ThreadLocalMap類型,並且都是Thread類的屬性,源碼如下:

    /* 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 如何實現值繼承的呢?繼續看下面的程式碼:

        /**           * 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;              int len = parentTable.length;              setThreshold(len);              table = new Entry[len];                for (int j = 0; j < len; j++) {                  Entry e = parentTable[j];                  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++;                      }                  }              }          }

在構造方法的完整源程式碼演算法中可以發現,子執行緒將父執行緒中的 table 對象以複製的方式賦值給子執行緒的 table 數組,這個過程是在創建 Thread 類對象時發生的,也就說明當子執行緒對象創建完畢後,子執行緒中的數據就是主執行緒中舊的數據,主執行緒使用新的數據時,子執行緒還是使用舊的數據,因為主子執行緒使用兩個 Entry[] 對象數組各自存儲自己的值。

這部分涉及到 Java 的值傳遞。對於對象來說,值的內容其實是對象的引用。當在父執行緒中修改對象的某一屬性,子執行緒由於引用著相同對象,所以可以感知到,本質上是在操作同一塊記憶體地址。

對於基本數據類型(int、long)來說,由於傳遞的是值,在父執行緒改變了數據後,子執行緒依舊使用的是舊的數據。這裡尤其要提 String 字元串,String 雖然不是基本數據類型,但是由於內部字元數組被 final 修飾帶來的不可變型,當父執行緒修改其 String 類型數據時,等於替換掉該 String 對象,而並不是修改原 String 對象的值,所以子執行緒依舊不會發生變化。

另外,重寫類 InheritableThreadLocal 的 childValue() 方法可以對繼承的值進行加工,比如通過調用clone() 方法返回 parentValue 的淺拷貝,以達到子執行緒無法影響父執行緒的目的。

程式碼如下:

public class Local extends InheritableThreadLocal {        @Override      protected Object initialValue() {          return new Date();      }        @Override      protected Object childValue(Object parentValue) {          return parentValue+"[子執行緒增強版]";  // parentValue.clone();      }  }