Threadlocal詳解(ThreadLocal,InheritTableThreadLocal,TransmittableThreadLocal)

xfgg發表於2020-10-24

參考:https://blog.csdn.net/upgroup/article/details/107935832
主要從四個問題了解
1)ThreadLocal可以用來解決什麼問題,你工作中或者使用的框架中哪裡用到了?
2)ThreadLocal解決不了什麼問題?怎麼辦
3)InheritThreadLocal解決不了什麼問題,怎麼辦
4)阿里的TransmittableThreadLocal是如何解決這個問題的

ThreadLocal可以用來解決什麼問題

一個變數的值想要線上程中傳遞,比如說分散式呼叫跟蹤系統中的traceID,RPC框架中傳遞上下文的context
保證執行緒不安全的類的安全,比如說SimpleDateFormate

ThreadLocal解決不了問題怎麼辦

ThreadLocal只是執行緒傳遞,但是當在本執行緒中建立了一個新的執行緒,比如說又new了一個Thread的情況下,就不能把ThreadLocal中的資料傳遞給子執行緒。此時解決辦法是通過InheritThreadLocal來解決

InheritThreadLocal解決不了什麼問題,怎麼辦

inheritThreadlocal解決了建立新的子執行緒的傳遞問題,但是如果用執行緒池建立執行緒的話,使用InheritThreadLocal可以保證資料的傳遞。但是執行緒池中的執行緒是重複使用的,當重複使用執行緒的時候,重複使用的執行緒中的InheritThreadLocal仍然是上次建立的資料。解決辦法就是阿里的TransmittableThreadLocal

阿里的TransmittableThreadLocal是如何解決這個問題的

阿里的解決方式仍然是使用的InheritThreadLocal,不同的是,阿里通過Javaagent修改了執行緒池的位元組碼,線上程池建立Runnable或者Callable的時候進行了包裝,我們就叫RunnableWrapper。把需要傳遞的資料在new RunnableWrapper的時候就傳遞到了RunnableWrapper的成員變數中。在RunnableWrapper執行run方法的時候,先將成員變數的資料重新放一遍ThreadLocal,然後再真正執行被包裝的Runnable的run方法。這樣在真正的run方法中就可以拿到ThreadLocal的資料

inheritThreadlocal詳解

inheritThreadlocal類

重寫了ThreadLocal的三個函式

   /**
     * 該函式在父執行緒建立子執行緒,向子執行緒複製InheritableThreadLocal變數時使用
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }
    /**
     * 由於重寫了getMap,操作InheritableThreadLocal時,
     * 將隻影響Thread類中的inheritableThreadLocals變數,
     * 與threadLocals變數不再有關係
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    /**
     * 類似於getMap,操作InheritableThreadLocal時,
     * 將隻影響Thread類中的inheritableThreadLocals變數,
     * 與threadLocals變數不再有關係
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

執行緒間傳值實現原理

首先講解一下Thread類

public class Thread implements Runnable {
   ......(其他原始碼)
    /* 
     * 當前執行緒的ThreadLocalMap,主要儲存該執行緒自身的ThreadLocal
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal,自父執行緒整合而來的ThreadLocalMap,
     * 主要用於父子執行緒間ThreadLocal變數的傳遞
     * 本文主要討論的就是這個ThreadLocalMap
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ......(其他原始碼)
}

Thread類包含Threadlocals和inherittableThreadLocals兩個變數inheritableThreadLocals 即主要儲存可自動向子執行緒中傳遞的ThreadLocal.ThreadLocalMap。
接下來看一下父執行緒建立子執行緒的流程,我們從最簡單的方式說起:
使用者建立Thread

Thread thread = new Thread();

Thread建立

    /**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, null, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
     */
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

Thread初始化

    /**
     * 預設情況下,設定inheritThreadLocals可傳遞
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
   /**
     * 初始化一個執行緒.
     * 此函式有兩處呼叫,
     * 1、上面的 init(),不傳AccessControlContext,inheritThreadLocals=true
     * 2、傳遞AccessControlContext,inheritThreadLocals=false
     */
    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);

        ......(其他程式碼)
    }

可以看到,採用預設方式產生子執行緒時,inheritThreadLocals=true;若此時父執行緒inheritableThreadLocals不為空,則將父執行緒inheritableThreadLocals傳遞至子執行緒。

ThreadLocal.createInheritedMap

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

 /**
         * 構建一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
         * 該函式只被 createInheritedMap() 呼叫.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            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];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        // 可能會有同學好奇此處為何使用childValue,而不是直接賦值,
                        // 畢竟childValue內部也是直接將e.value返回;
                        // 個人理解,主要為了減輕閱讀程式碼的難度
                        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++;
                    }
                }
            }
        }

子執行緒將parentMap逐一複製到自身執行緒
InheritableThreadLocal主要用於子執行緒建立時,需要自動繼承父執行緒的ThreadLocal變數,方便必要資訊的進一步傳遞。

TransmittableThreadLocal詳解

用於解決使用執行緒池快取執行緒的元件的情況下傳遞ThreadLocal
使用場景:分散式跟蹤系統,應用容器或上下層框架跨應用程式碼給下層SDK傳遞資訊,日誌收集系統上下文,

原始碼分析:

TransmittableThreadLocal 繼承自 InheritableThreadLocal,這樣可以在不破壞ThreadLocal 本身的情況下,使得當使用者利用 new Thread() 建立執行緒時仍然可以達到傳遞InheritableThreadLocal 的目的。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T>{......}

TransmittableThreadLocal相比於InheritableThreadLocal比較關鍵的一點是引入了holder變數,這樣就不必對外暴露Thread中的inherittablethreadlocals變數儲存ThreadLocalMap的封裝性

// 理解holder,需注意如下幾點:
// 1、holder 是 InheritableThreadLocal 變數;
// 2、holder 是 static 變數;
// 3、value 是 WeakHashMap;
// 4、深刻理解 ThreadLocal 工作原理;
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
      new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
          @Override
          protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
              return new WeakHashMap<>();
          }

          @Override
          protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
              return new WeakHashMap<>(parentValue);
          }
      };
// 呼叫 get() 方法時,同時將 this 指標放入 holder
public final T get() {
    T value = super.get();
    if (null != value) {
        addValue();
    }
    return value;
}
void addValue() {
    if (!holder.get().containsKey(this)) {
        holder.get().put(this, null); // WeakHashMap supports null value.
    }
}
// 呼叫 set() 方法時,同時處理 holder 中 this 指標
public final void set(T value) {
    super.set(value);
    if (null == value) { // may set null to remove value
        removeValue();
    } else {
        addValue();
    }
}
void removeValue() {
    holder.get().remove(this);
}

個人感覺是在inheritThreadlocal裡面在加入一個inheritThreadLocal變數對Threadlocals變數進行封裝,保證執行緒安全

工作流程簡介

自定義TtlRunnable實現Runnable,TtlRunnable初始化方法中保持當前執行緒中已有的TransmittableThreadLocal

private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}

執行緒池中執行緒呼叫run方法,執行前先backup holder中所有的TransmittableThreadLocal, copiedRef中不存在,holder存在的,說明是後來加進去的,remove掉holder中的;將copied中的TransmittableThreadLocal set到當前執行緒中

public void run() {
    Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
    if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }

    Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
    try {
        runnable.run();
    } finally {
        TransmittableThreadLocal.restoreBackup(backup);
    }
}

執行後再恢復 backup 的資料到 holder 中(backup中不存在,holder中存在的TransmittableThreadLocal,從holder中remove掉),將 backup 中的 TransmittableThreadLocal set到當前執行緒中

相關文章