父子線程和線程池如何實現threadLocal變量傳遞

ThreadLocal的原理和實現,今天咱們看看下面幾個問題:java

1.多線程中父子線程,子線程如何獲取父線程的變量?
2.主線程和線程池的線程本地副本變量如何實現複用隔離?
緩存

1、InheritableThreadLocal的使用

       多線程中父子線程,子線程如何獲取父線程的變量?下面就是InheritableThreadLocal的示例:微信

 

public static void main(String[] args{
    InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
    java.lang.ThreadLocal threadLocal = new ThreadLocal();
    inheritableThreadLocal.set("inheritableThreadLocal-value");
    threadLocal.set("threadLocal-value");
    System.out.println("main thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
    System.out.println("main thread --- threadLocal:"+threadLocal.get());
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() 
{
            System.out.println("new thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
            System.out.println("new thread --- threadLocal:"+threadLocal.get());
        }
    });
    thread.start();
}
多線程

運行結果:框架

 

main thread --- inheritableThreadLocal:inheritableThreadLocal-value
main thread --- threadLocal:threadLocal-value
new thread --- inheritableThreadLocal:inheritableThreadLocal-value
new thread --- threadLocal:null
ide

     上面例子能夠看出inheritableThreadLocal能夠在子線程獲取值,可是threadLocal不能夠。InheritableThreadLocal其實繼承ThreadLocal,其中重寫了幾個方法來操做ThreadLocalMap。主線程建立新線程的時候會獲取當前線程的inheritableThreadLocals,而且將當前線程的該變量賦值給inheritableThreadLocals。這裏在子線程修改引用變量的值,父線程也是能夠獲取的。函數

2、TransmittableThreadLocal

        TransmittableThreadLocal 是Alibaba開源的封裝類。解決線程池線程或者緩存線程框架的ThreadLocal複用傳遞問題,需配合 TtlRunnable 和 TtlCallable 使用。下面看看TransmittableThreadLocal如何實現主線程和線程池中線程ThreadLocal複用和隔離的。this

 

 public static void main(String[] args{
    InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
    //ExecutorService裝換,將runnable轉爲TtlRunnable,爲了配合TransmittableThreadLocal使用,沒有其餘邏輯,能夠理解和普通線程池同樣
    ExecutorService pool =  TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(3));

    for(int i=0;i<5;i++) {
        int j = i;
        //賦值threadLocal
        inheritableThreadLocal.set("inheritableThreadLocal-value-"+j);
        transmittableThreadLocal.set("transmittableThreadLocal-value-"+j);
        pool.execute(new Thread(new Runnable() {
            @Override
            public void run() 
{
                pool.execute(new Runnable() {
                    @Override
                    public void run() 
{
                        System.out.println(Thread.currentThread().getName() + " : " + inheritableThreadLocal.get()+"-----"+transmittableThreadLocal.get());
                    }
                });
            }
        }));
    }
}
spa

運行結果:.net

 

pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-0
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-3
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-4
pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-1
pool-1-thread-3 : inheritableThreadLocal-value-2-----transmittableThreadLocal-value-2

結果分析:

  1. inheritableThreadLocal在主線程和線程池之間能夠傳遞的,可是歸還線程池的線程inheritableThreadLocal值是一直不變的,若是是新開闢的線程,則會傳遞。因此inheritableThreadLocal仍是不能解決線程池和主線程之間的複用問題,由於緩存的線程會直接使用老的inheritableThreadLocal的值

  2. 能夠看出transmittableThreadLocal 能夠實現主線程和線程池之間的複用傳遞,無論是不是已緩存的線程,均可以實現線程池線程之間的複用隔離效果。

3、TransmittableThreadLocal源碼

    首先看一下TransmittableThreadLocal的主要設計邏輯是什麼:

1.首先須要使用輔助線程類TtlRunnable封裝了Runnable執行邏輯;
2.而後TtlRunnable中會TransmittableThreadLocal.copy()獲取當前父線程的Map<TransmittableThreadLocal<?>, Object>;
3.執行前會經過backupAndSetToCopied賦值給當前線程;
4.線程執行完run方法後,經過restoreBackup()再備份一遍。我想應該是防止你線程進行修改。

 

//構造TtlRunnable
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
    if (null == runnable) {
        return null;
    }
    if (runnable instanceof TtlRunnable) {
        if (idempotent) {
            //避免多餘的裝飾,並確保冪等性
            return (TtlRunnable) runnable;
        } else {
            throw new IllegalStateException("Already TtlRunnable!");
        }
    }
    return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
//構造函數
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    //1.這裏將父線程的TransmittableThreadLocal變量值進行一次深拷貝
    this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
 * TtlRunnable的run函數
 */

@Override
public void run() {
    //2.獲取當前父線程的 TransmittableThreadLocal對象
    Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
    if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
    //3.TransmittableThreadLocal對象賦值給線程池該執行線程的ThreadLocal,而且返回出來
    Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);

    try {
        //4.這裏在正常執行線程,此時的線程已經有了最新的TransmittableThreadLocal對象
        //若是是純InheritableThreadLocal,是沒有任何效果的
        runnable.run();
    } finally {
        //6.最終將一開始的Map<TransmittableThreadLocal<?>, Object>恢復,防止你線程進行修改
        TransmittableThreadLocal.restoreBackup(backup);
    }
}

總結

      從上面幾個例子中能夠得出,父子線程之間想要傳遞線程本地變量須要依賴InheritableThreadLocal,此時ThreadLocal變量只能做用單獨的線程。可是線程池等線程緩存類框架中,要想實現主線程和線程池之間實現複用隔離效果,則能夠使用TransmittableThreadLocal來完成。

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