從ThreadLocal 到TransmittableThreadLocal

如何處理線程上下文之間的參數透傳


1,ThreadLocal 的使用場景

1.1 介紹:

ThreadLocal是Java中的一個類,可以實現在多線程環境下安全地存儲和訪問線程局部變量。它可以看作是一個容器,用于存儲特定線程所需的數據。

在多線程編程中,每個線程都有自己的執行上下文,包括自己的棧、本地變量和程序計數器等。線程局部變量就是指在每個線程的執行上下文中的本地變量。ThreadLocal可以幫助我們在多線程環境下,為每個線程維護一個獨立的局部變量副本,從而避免多線程競爭和數據混亂的問題。

1.2 原理:

  1. 每個線程維護一個ThreadLocalMap

    ThreadLocalMap的鍵為ThreadLocal實例,即每個線程局部變量對應的ThreadLocal對象。這是因為ThreadLocal是線程局部變量的抽象類,每個ThreadLocal實例對應著一個獨立的線程局部變量。
    在ThreadLocalMap中,ThreadLocal作為鍵,其內部實現是一個自定義的Entry數組,Entry中存儲了鍵值對的信息,即ThreadLocal實例作為鍵,對應的線程局部變量副本作為值。當線程調用get()、set()和remove()等方法時,ThreadLocalMap會根據當前線程對象找到對應的Entry,并返回或者修改其中的值。
    值得注意的是,ThreadLocal實例是弱引用,這意味著如果ThreadLocal實例沒有被任何線程引用時,可能會被垃圾回收機制回收。如果ThreadLocal實例被回收后,對應的鍵值對也會被清除,這就是ThreadLocal內存泄漏的主要原因之一。為了避免內存泄漏,我們通常會在不需要使用ThreadLocal時顯式地調用remove()方法來清除鍵值。

1.3 使用場景

ThreadLocal的使用場景主要涉及到需要在多線程環境下實現數據隔離的情況

  1. 線程內部的變量共享問題:當在多個線程之間需要共享一個變量時,但又要保證線程安全,此時可以使用ThreadLocal實現局部變量的隔離,每個線程都有自己的副本,互不干擾。
  2. 用戶信息:在Web應用中,需要存儲用戶信息或者會話信息,在多線程環境下,如果不使用ThreadLocal會產生線程安全問題,此時應該使用ThreadLocal來解決問題。
  3. 線程活動的狀態:有些情況下,我們需要記錄或者保存線程的狀態,這時使用ThreadLocal來存儲線程狀態是比較合適的選擇。

1.4 總結

總之,如果需要在多線程環境下實現數據隔離,或者需要跨線程存儲變量信息,就可以考慮使用ThreadLocal來解決問題。但需要注意,使用ThreadLocal也可能存在內存泄漏的問題,使用時需要仔細設計和管理。

2,InheritableThreadLocal 跨線程使用ThreadLocal

2.1 介紹

多數情況下不會出現跨線程的使用,如果是需要線程上下文的情景,可能會跨線程

  • InheritableThreadLocal 的介紹
InheritableThreadLocal是ThreadLocal的一個變種,它允許在父子線程之間繼承值。與ThreadLocal不同的是,當一個新線程由另一個線程(即父線程)創建時,它會從父線程中的InheritableThreadLocal復制值。 
InheritableThreadLocal除了實現了ThreadLocal的功能外,還增加了子線程從父線程那里繼承ThreadLocal值的功能,這種特性使其更適合在多線程應用程序中傳遞上下文信息。

2.2 為什么InheritableThreadLocal 父子線程可以實現 ThreadLocal 的傳遞

這是由線程的創建決定的

//創建線程的構造方法,其中父子線程的傳遞在init 實現
public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
  1. init 方法的實現

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
       ...省略其他代碼,只保留核心
       //inheritThreadLocals 是有線程維護的InheritableThreadLocalcal 重寫的getMap 就是獲取此變量,此變量也是一個threadLocalMap
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //
            this.inheritableThreadLocals =
    //再此實現父ttl 的值復制        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
         
    }
    
    //創建map
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
        //復值ttl 核心代碼
        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) {
                        //childValue 就是 InheritableThreadLocalcal
                        重寫的方法,返回父線程的值
                            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++;
                        }
                    }
                }
            }
    

2.3 代碼演示

2.3.1 普通ThreadLocal 的使用,用父子線程的值傳遞

@Test
public void test1() {

    ThreadLocal<String> local = new ThreadLocal<>();
    local.set("init");
    log.info("線程:{}獲取threadLocal:{} 值", Thread.currentThread().getName(), local.get());
    //子線程獲取
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            log.info("子線程:{}獲取threadLocal:{} 值", Thread.currentThread().getName(), local.get());
        }
    });
    thread.start();
}
//
18:04:48.220 [main] INFO com.h3c.mp.thread.ThreadTest3 - 線程:main獲取threadLocal:init 值
18:04:48.235 [Thread-0] INFO com.h3c.mp.thread.ThreadTest3 - 子線程:Thread-0獲取threadLocal:null

2.3.2 InheritableThreadLocalcal 的演示與使用

 @Test
    public void test2() {
        InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
        local.set("init");
        log.info("線程:{}獲取threadLocal:{} 值", Thread.currentThread().getName(), local.get());
        //子線程獲取
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log.info("子線程:{}獲取threadLocal:{} 值", Thread.currentThread().getName(), local.get());

            }
        });
        thread.start();
    }
18:08:20.291 [main] INFO com.h3c.mp.thread.ThreadTest3 - 線程:main獲取threadLocal:init 值
18:08:20.345 [Thread-0] INFO com.h3c.mp.thread.ThreadTest3 - 子線程:Thread-0獲取threadLocal:init 值

2.4 總結

因此 InheritableThreadLocal 可以用來做父子線程的值參數傳遞

但是 由源碼可以看出,真正進行值傳遞時,是在線程創建的時候觸發執行的,所以在使用線程池的時候,當發生線程復用時,可能造成值獲取錯亂

3 , Ttl 技術的使用

3.1 介紹

JDKInheritableThreadLocal類可以完成父線程到子線程的值傳遞。但對于使用線程池等會池化復用線程的執行組件的情況,線程由線程池創建好,并且線程是池化起來反復使用的;**這時父子線程關系的ThreadLocal值傳遞已經沒有意義,應用需要的實際上是把 任務提交給線程池時的ThreadLocal值傳遞到 任務執行時****。

3.2 原理

3.2.1 簡單模擬

  public final class DelegatingContextRunnable implements Runnable {

    private final Runnable delegate;

    private final Optional<String> delegateContext;

    public DelegatingContextRunnable(Runnable delegate,
                                       Optional<String> context) {
        assert delegate != null;
        assert context != null;

        this.delegate = delegate;
        this.delegateContext = context;
    }

    public DelegatingContextRunnable(Runnable delegate) {
        // 修飾原有的任務,并保存當前線程的值
        this(delegate, ContextHolder.get());
    }

    public void run() {
        Optional<String> originalContext = ContextHolder.get();

        try {
            ContextHolder.set(delegateContext);
            delegate.run();
        } finally {
            ContextHolder.set(originalContext);
        }
    }
}

public final void execute(Runnable task) {
  // 遞交給真正的執行線程池前,對任務進行修飾
  executor.execute(wrap(task));
}

protected final Runnable wrap(Runnable task) {
  return new DelegatingContextRunnable(task);
}

總結: 實際上通過三部,

  1. 獲取提交任務時的線程ttl
  2. 重放提交任務時的threadLocal
  3. 恢復執行任務是線程的threadLocal

3.3 Tt l 技術的實現

3.3.1 github 地址

https://github.com/alibaba/transmittable-thread-local

3.3.2 maven 依賴

         <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.14.2</version>
         </dependency>

3.3.3 核心類 TransmittableThreadLocal

使用類TransmittableThreadLocal來保存值,并跨線程池傳遞。

TransmittableThreadLocal繼承InheritableThreadLocal,使用方式也類似。相比InheritableThreadLocal,添加了protectedtransmitteeValue()方法,用于定制 任務提交給線程池時ThreadLocal值傳遞到 任務執行時 的傳遞方式,缺省是簡單的賦值傳遞。

3.3.4 簡單使用

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父線程中設置
context.set("value-set-in-parent");

// =====================================================

// 在子線程中可以讀取,值是"value-set-in-parent"
String value = context.get();

3.3.5 線程池中傳值

  • 修飾Runnable

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父線程中設置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
// 額外的處理,生成修飾了的對象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);

// =====================================================

// Task中可以讀取,值是"value-set-in-parent"
String value = context.get();
  • 修飾線程池

    省去每次RunnableCallable傳入線程池時的修飾,這個邏輯可以在線程池中完成。

    通過工具類TtlExecutors完成,有下面的方法:

    • getTtlExecutor:修飾接口Executor
    • getTtlExecutorService:修飾接口ExecutorService
    • getTtlScheduledExecutorService:修飾接口ScheduledExecutorService
 ExecutorService executorService = ...
// 額外的處理,生成修飾了的對象executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父線程中設置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);

// =====================================================

// Task或是Call中可以讀取,值是"value-set-in-parent"
String value = context.get();

3.4 TtlRunnable 的實現

3.4.1 TtlRunnable 的 核心源碼

//省略非核心代碼
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
   //存放上下文
    private final AtomicReference<Object> capturedRef;
    private final Runnable runnable;
    private final boolean releaseTtlValueReferenceAfterRun;

    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
       this.capturedRef = new AtomicReference<Object>( capture());
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
     }

    /**
     * wrap method {@link Runnable#run()}.
     */
    @Override
    public void run() {

        //@2去出任務提交時的線程上下文
        final Object captured = capturedRef.get();
        //
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //@3  重放父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,并返回原先存在與子線程中的上下文環境變量
        final Object backup = replay(captured);
        try {

            runnable.run();
        } finally {
            //@4恢復線程池中當前執行任務的線程的上下文環境 ,會直接繼承父線程中的上下文環境,但會將原先存在該線程的線程上下文環境進行備份,在任務執行完后通過執行restore方法進行恢復
            restore(backup);

        }
    }
    }
  • captured == null:這個條件檢查了保存的上下文引用是否為null。如果為null,意味著沒有保存的上下文,可能是因為沒有正確捕獲上下文或者上下文已經被釋放。
  • releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null):這個條件判斷了是否需要在任務執行后釋放保存的上下文引用,并嘗試將保存的上下文引用設為null。如果releaseTtlValueReferenceAfterRuntrue且無法將保存的上下文引用設為null,意味著上下文引用在任務執行后已經被釋放或被其他線程修改。

如果上述任何一個條件判斷為true,則會拋出IllegalStateException異常,表示保存的上下文引用在任務執行后已經被釋放,出現了異常情況。

這個檢查的目的是確保在任務執行前保存的上下文引用在執行期間是有效的,以避免在無效的上下文環境中執行任務導致的問題。

  • ==capture()方法的話,這里的操作其實就是將父線程的TTL變量集合生成相應的快照記錄,并隨著任務創建包裝的時候,保存到生成的 AtomicReference<Object> capturedRef;,由此實現了異步線程在線程池下的變量傳遞==。

  • ==replay(captured) 重放父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,并返回原先存在與子線程中的上下文環境變量==

  • ==restore(backup) 隨后恢復線程的任務至初始的備份狀態==;

  • ==capture() 和剩余的replay 和restore ,大多數不在一個線程,==

3.5 TransmittableThreadLocal 的實現與核心原理

3.5.1 核心holder

// Note about the holder:
    // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*).
    // 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal<Object>, ?>.
    //    2.1 but the WeakHashMap is used as a *Set*:
    //        the value of WeakHashMap is *always* null, and never used.
    //    2.2 WeakHashMap support *null* value.
     private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
            new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
                    //創建一個初始為null的set集合
                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
                }

                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
                     return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
                }
            };

==這段代碼創建了一個匿名內部類,并通過重寫initialValue()childValue()方法來指定InheritableThreadLocal的初始值和子線程值的生成方式==

  • initialValue()方法:這個方法在獲取線程本地變量的初始值時被調用。在這里,它返回一個WeakHashMap<TransmittableThreadLocal<Object>, ?>對象,作為初始值存儲在當前線程中。
  • childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue)方法:這個方法在父線程中設置了線程本地變量后,在子線程中獲取該變量時被調用。它接收父線程中的值作為參數,并返回一個新的WeakHashMap<TransmittableThreadLocal<Object>, ?>對象,將父線程的值復制到子線程中。

這個holder變量的作用是在線程之間傳遞TransmittableThreadLocal對象和其對應的值的映射關系。通過使用InheritableThreadLocal,它可以確保在父線程中設置的TransmittableThreadLocal對象和值在子線程中可以繼承和訪問。這對于實現線程上下文傳遞非常有用,可以在不同線程之間共享上下文信息

3.5.2 ==holder 的 作用及原理==

  • TransmittableThreadLocal set 方法
@Override
public final void set(T value) {
    if (!disableIgnoreNullValueSemantics && null == value) {
        // may set null to remove value
        remove();
    } else {
        super.set(value);
        addThisToHolder();
    }
}
  • TransmittableThreadLocal 的方法

    public final T get() {
        T value = super.get();
         if (disableIgnoreNullValueSemantics || null != value) {
    
            addThisToHolder();
        }
        return value;
    }
    
  • ==addThisToHolder==

    private void addThisToHolder() {
        if (!holder.get().containsKey(this)) {
            WeakHashMap<TransmittableThreadLocal<Object>, ?> transmittableThreadLocalWeakHashMap = holder.get();
            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
        }
    }
    
  • holder.get():這個語句獲取了當前線程中存儲的WeakHashMap<TransmittableThreadLocal<Object>, ?>對象,它是holder的值,用于存儲TransmittableThreadLocal對象和其對應的值的映射關系。

  • !holder.get().containsKey(this):這個條件判斷檢查當前的TransmittableThreadLocal對象是否已經存在于holder中。如果不存在,則執行后續操作。

  • transmittableThreadLocalWeakHashMap:這個變量引用了當前線程中存儲的WeakHashMap<TransmittableThreadLocal<Object>, ?>對象,方便后續操作。

  • holder.get().put((TransmittableThreadLocal<Object>) this, null):這行代碼將當前的TransmittableThreadLocal對象作為鍵,null作為值,添加到holder中。WeakHashMap支持null值。

這個方法的作用是將當前的TransmittableThreadLocal對象添加到holder中,確保線程在使用該對象時可以從holder中獲取對應的值。通過containsKey()方法判斷對象是否已存在于holder中,避免重復添加。

  • holder存儲的是所有線程在運行過程中的TransmittableThreadLocal對象和其對應的值的映射關系。

    在使用TransmittableThreadLocal進行線程間傳遞時,每個線程都可以通過holder來獲取自己線程中存儲的TransmittableThreadLocal對象和值的映射關系。這樣就可以在不同的線程中獲取和設置相應的上下文信息。

    當一個線程設置了TransmittableThreadLocal對象和值后,它會將該對象添加到holder中,以便其他線程可以從holder中獲取到相應的對象和值。這樣就實現了線程間的上下文傳遞和共享。

    需要注意的是,holder中使用WeakHashMap來存儲映射關系。WeakHashMap的特性是當TransmittableThreadLocal對象沒有被其他對象引用時,即沒有強引用存在時,該映射關系會被自動清除,從而避免內存泄漏。這樣可以確保只有活躍的線程和相關的TransmittableThreadLocal對象被保留在holder中。

3.6 ExecutorTtlWrapper 的實現

3.6.1 核心源碼

class ExecutorTtlWrapper implements Executor, TtlWrapper<Executor>, TtlEnhanced {
    private final Executor executor;
    protected final boolean idempotent;

    ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) {
        this.executor = executor;
        this.idempotent = idempotent;
    }

    @Override
    public void execute(@NonNull Runnable command) {
        executor.execute(TtlRunnable.get(command, false, idempotent));
    }

    @Override
    @NonNull
    public Executor unwrap() {
        return executor;
    }

}

3.6.2 使用

public final class TtlExecutors {

public static Executor getTtlExecutor(@Nullable Executor executor) {
    if (TtlAgent.isTtlAgentLoaded() || null == executor || executor instanceof TtlEnhanced) {
        return executor;
    }
    return new ExecutorTtlWrapper(executor, true);
}
   //...省略 
}

//實際還是修飾runnable

public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
    if (null == runnable) return null;

    if (runnable instanceof TtlEnhanced) {
        // avoid redundant decoration, and ensure idempotency
        if (idempotent) return (TtlRunnable) runnable;
        else throw new IllegalStateException("Already TtlRunnable!");
    }
    return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}

3.7 CRR 模式的介紹

  • TtlRunnable 的 run

    @Override
    public void run() {
    
        //@2去出任務提交時的線程上下文
        final Object captured = capturedRef.get();
        //
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //@3  重放父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,并返回原先存在與子線程中的上下文環境變量
        final Object backup = replay(captured);
        try {
    
            runnable.run();
        } finally {
            //@4恢復線程池中當前執行任務的線程的上下文環境, ,會直接繼承父線程中的上下文環境,但會將原先存在該線程的線程上下文環境進行備份,在任務執行完后通過執行restore方法進行恢復
            restore(backup);
    
        }
    }
    

上下文的傳遞流程或說生命周期可以規范化成:捕捉、回放和恢復這3個操作

  • 框架/中間件集成TTL傳遞,通過TransmittableThreadLocal.Transmitter 抓取當前線程的所有TTL值并在其他線程進行回放;在回放線程執行完業務操作后,恢復為回放線程原來的TTL值。

    TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢復方法(即CRR操作):

    1. capture方法:抓取線程(線程A)的所有TTL值。

    2. replay方法:在另一個線程(線程B)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的備份

    3. restore方法:恢復線程B執行replay方法之前的TTL值(即備份)

3.7.1線程池的拒絕策略

  • 線程池的拒絕策略

    1. AbortPolicy(默認策略):當線程池無法接受新任務時,會直接拋出RejectedExecutionException異常,拒絕執行新的任務。
    2. CallerRunsPolicy:當線程池無法接受新任務時,會使用提交任務的線程來執行該任務。也就是說,如果線程池飽和了,新任務會由提交任務的線程直接執行,這樣可以避免任務丟失,但是會影響提交任務的線程的性能。
    3. DiscardPolicy:當線程池無法接受新任務時,會直接丟棄這個任務,不會拋出任何異常。如果對任務的執行結果沒有特殊要求,且不關心任務是否被執行,可以選擇此策略。
    4. DiscardOldestPolicy:當線程池無法接受新任務時,會丟棄線程池中最早被提交的任務,然后嘗試再次提交新任務。

    除了上述四種常見的拒絕策略外,Java中的線程池還提供了一種可自定義的拒絕策略:

    1. 自定義拒絕策略:可以實現RejectedExecutionHandler接口,并自定義拒絕策略的具體行為。可以根據實際需求,自定義拒絕策略來處理被拒絕的任務。例如,可以將被拒絕的任務重新放入隊列等待執行,或者記錄日志等。

    在使用線程池時,可以根據實際需求選擇合適的拒絕策略,以便對被拒絕的任務進行適當的處理。

    3.7.2 線程池的執行流程

    [圖片上傳失敗...(image-68a131-1688372604519)]

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// (1) 抓取當前線程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 線程 B(異步線程)
// ===========================================================================

// (2) 在線程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的備份
final Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
    // 你的業務邏輯,這里你可以獲取到外面設置的TTL值
    String value = context.get();

    System.out.println("Hello: " + value);
    ...
    String result = "World: " + value;
} finally {
    // (3) 恢復線程 B執行replay方法之前的TTL值(即備份)
    TransmittableThreadLocal.Transmitter.restore(backup);
}

3.7.2為甚麼要replay (回放線程值)

  • 在回放線程B執行replay方法之前的TTL值時,是為了確保線程B在執行業務邏輯時能夠使用原始的TTL值。這是因為在回放過程中,TTL值被修改為線程A中的值,而不是線程B的原始值。

3.7.3為什么要restore

  1. 當線程B執行replay方法時,它會從線程A中回放TTL值。這樣做是為了使線程B能夠訪問到在線程A中設置的TTL值,以便在執行業務邏輯時使用。然而,一旦回放完成,線程B可能需要恢復到它自己的原始TTL值,以確保不會影響后續的操作或其他線程。
  2. 通過調用TransmittableThreadLocal.Transmitter的restore方法,可以將回放之前捕獲的TTL值恢復回線程B的原始狀態。這樣,線程B在執行完業務邏輯后,就可以繼續使用它自己的TTL值,而不會受到線程A中設置的TTL值的影響。
  3. 回復以恢復線程B執行replay方法之前的TTL值是為了確保線程的隔離性和正確性。它允許每個線程在自己的上下文中執行,并在需要的時候共享TTL值,而不會相互干擾或造成不一致的狀態。這對于需要在線程之間傳遞上下文或狀態信息的場景非常重要,以保持正確的業務邏輯執行和數據一致性。

3.7.4 backup 什么時候有值

  • 典型的業務場景下,replay操作的線程,與來源的capture線程,是不同的。
  • capture的線程 在 業務中立的線程池 時,這樣的線程 往往 也沒有/不需要 有上下文。

這2個前提成立時,backup往往 不會有值。

當上面2點不成立時,如

  • 上面提到的場景,線程池滿了 且 線程池使用的是CallerRunsPolicy
    則 提交到線程池的任務 在capture線程直接執行,也就是 直接在業務線程中同步執行;
  • 使用ForkJoinPool(包含并行執行StreamCompletableFuture,底層使用ForkJoinPool)的場景,展開的ForkJoinTask會在調用線程中直接執行。

這時 backup是有值的,如果不做restore backup業務線程里的上下文就丟了,
業務后續的執行就會有Bug

3.8 時序圖

[圖片上傳失敗...(image-28f06b-1688372604519)]

3.9 CRR 核心源碼

public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
    private final AtomicReference<Object> capturedRef;
    private final Runnable runnable;
    private final boolean releaseTtlValueReferenceAfterRun;

    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
       //@1 捕捉上下文
        this.capturedRef = new AtomicReference<Object>(capture());
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
     }

    /**
     * wrap method {@link Runnable#run()}.
     */
    @Override
    public void run() {

        //@2去出任務提交時的線程上下文
        final Object captured = capturedRef.get();
        //
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //@3  重放父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,并返回原先存在于子線程中的上下文環境變量
        final Object backup = replay(captured);
        try {

            runnable.run();
        } finally {
            //@4恢復線程池中當前執行任務的線程的上下文環境, ,會直接繼承父線程中的上下文環境,但會將原先存在該線程的線程上下文環境進行備份,在任務執行完后通過執行restore方法進行恢復
            restore(backup);

        }
    }
    }

3.9.1 ==capture() 方法的實現(此過程邏輯實際發生在提交任務的線程)==

快照對象 (實際上是對父線程的 ttl 的一個快照 )

  private static class Snapshot {
            final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
            final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
            private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
                this.ttl2Value = ttl2Value;
                this.threadLocal2Value = threadLocal2Value;
            }
        }

Snapshot 類包含兩個字段:

  1. ttl2Value:一個 HashMap 對象,用于存儲 TransmittableThreadLocal 對象及其對應的值。TransmittableThreadLocal 是一個特殊的線程局部變量,可以在線程間傳遞值。
  2. threadLocal2Value:一個 HashMap 對象,用于存儲普通的 ThreadLocal 對象及其對應的值。

這個 Snapshot 類主要用于在多線程環境下保存線程局部變量的快照,以便后續在其他線程中回放這些值。它將 TransmittableThreadLocal 和普通的 ThreadLocal 對象及其對應的值存儲在兩個 HashMap 中,以便在需要時進行檢索和恢復。

通常情況下,Snapshot 對象會在線程切換時創建,以捕獲當前線程的線程局部變量的值,并用于后續的傳遞和回放操作。

  • holder
    private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
        new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
            @Override
            protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
                //get 的時候調用
                return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
            }

            @Override
            protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
                //set 的時候調用
                return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
            }
        };
  • captureTtlValues() 的實現
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
    HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
    WeakHashMap<TransmittableThreadLocal<Object>, ?> transmittableThreadLocalWeakHashMap = holder.get();
    //@2
    for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
    //@3
        Object o = threadLocal.copyValue();
        ttl2Value.put(threadLocal, o);
    }
    return ttl2Value;
}
  1. @1holder 實際上就是一個可在父子線程之間傳遞的threadLocal 存儲的是一個Map 類型的值, key 為TransmittableThreadLocal

  2. @2 通過 holder.get() 方法獲取當前線程的 WeakHashMap 對象 ,該 WeakHashMap 存儲了當前線程的所WeakHashMap<TransmittableThreadLocal<Object>, ?> 有 TransmittableThreadLocal 對象及其對應的值 比如當前線程 有2個TransmittableThreadLocal

    分別為TransmittableThreadLocal<String> local1 和 TransmittableThreadLocal local2

  3. @3 實際上是

       private T copyValue() {
           //get() 就是ThreadLocal.get()
            return copy(get());
        }
    //copy 方法是TransmittableThreadLocal 實現了TtlCopier 接口 用來拓展,threadLocal 值傳遞的方法,深拷貝,還是淺拷貝
    @FunctionalInterface
    public interface TtlCopier<T> {
        
        T copy(T parentValue);
    }
    
       public T copy(T parentValue) {
            return parentValue;
        }
    
  • captureThreadLocalValues() 的實現 實際上是抓取當前線程普通的threadLocal 值,用來增強ThreadLocal 使用的

    private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> 
    threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>();
    
      public static <T> boolean registerThreadLocal(@NonNull ThreadLocal<T> threadLocal, 
                                                    @NonNull TtlCopier<T> copier, boolean force) {
                if (threadLocal instanceof TransmittableThreadLocal) {
                    logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!");
                    return true;
                }
                synchronized (threadLocalHolderUpdateLock) {
                    if (!force && threadLocalHolder.containsKey(threadLocal)) return false;
    
                    WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> newHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(threadLocalHolder);
                    newHolder.put((ThreadLocal<Object>) threadLocal, (TtlCopier<Object>) copier);
                    threadLocalHolder = newHolder;
                    return true;
                }
            }
    

private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
    final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
    for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        final TtlCopier<Object> copier = entry.getValue();
        Object copy = copier.copy(threadLocal.get());

        threadLocal2Value.put(threadLocal, copy);
    }
    return threadLocal2Value;
}

3.9.2 ==replay(captured) 重放父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,并返回原先存在于子線程中的上下文環境變量==

public static Object replay(@NonNull Object captured) {
    final Snapshot capturedSnapshot = (Snapshot) captured;
    return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
  • replayTtlValues(capturedSnapshot.ttl2Value)

    用來重放并備份執行任務線程的ttl2Value

    @NonNull
    private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
    //@1存儲執行任務線程的ttl2Value 
        HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
         // 
        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        //獲取線程可能是繼承來的
            TransmittableThreadLocal<Object> threadLocal = iterator.next();
    
            // backup
            backup.put(threadLocal, threadLocal.get());
     
            @2 如果父線程快照中沒有包含的threadLocal
            if (!captured.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }
    
        // set TTL values to captured
        //把線程的上下文復制到當前的線程
        setTtlValuesTo(captured);
    
        // call beforeExecute callback
        doExecuteCallback(true);
    
        return backup;
    }
    
  1. @1用來備份執行任務線程的threadLocal

  2. @2 如果 captured 不包含當前的 threadLocal,則將其從 holder 中移除,并調用 threadLocal.superRemove() 方法進行清理操作的目的是清除在回放線程中不再需要的 TransmittableThreadLocal

    當執行回放操作時,captured 中只包含在抓取階段捕獲的 TransmittableThreadLocal 對象及其對應的值。如果在回放階段發現某個 TransmittableThreadLocal 對象在 captured 中不存在,說明該對象在回放線程中不再需要使用。

    因此,為了避免回放線程中出現多余的 TransmittableThreadLocal 對象,需要將其從 holder 中移除,并調用 threadLocal.superRemove() 方法進行清理操作。這樣可以確保在回放線程中只保留需要的 TransmittableThreadLocal,避免產生不必要的內存占用 (舉例,執行前該線程 只有一個TransmittableThreadLocal<Integer> ,在執行中 又創建了新的TransmittableThreadLocal<String> ,實際上我們只要提交任務前的變量值)

private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
    for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
        TransmittableThreadLocal<Object> threadLocal = entry.getKey();
        threadLocal.set(entry.getValue());
    }
}

當在子線程中調用父線程的 TransmittableThreadLocal 對象的 set 方法時,實際上會在子線程中創建一個新的 TransmittableThreadLocal 對象,并將父線程的值復制到子線程的對象中。

3.9.3 restore(backup) 的實現

public static void restore(@NonNull Object backup) {
    final Snapshot backupSnapshot = (Snapshot) backup;
    restoreTtlValues(backupSnapshot.ttl2Value);
    restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
    // call afterExecute callback
    doExecuteCallback(false);
    WeakHashMap<TransmittableThreadLocal<Object>, ?> transmittableThreadLocalWeakHashMap = holder.get();
    for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        TransmittableThreadLocal<Object> threadLocal = iterator.next();

        // clear the TTL values that is not in backup
        // avoid the extra TTL values after restore
        //刪除異步操作中新增的threadLocal 變量
        if (!backup.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }

    // restore TTL values
    setTtlValuesTo(backup);
}


private static void restoreThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> backup) {
    for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        threadLocal.set(entry.getValue());
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容