自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文徹底搞懂阿里開(kāi)源 TransmittableThreaLocal 的原理和使用

開(kāi)發(fā) 開(kāi)源
本文通過(guò)代碼示例依次演示ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal實(shí)現(xiàn)父子線程傳參演化過(guò)程。

今天來(lái)聊一聊阿里的 TTL 也就是TransmittableThreadLocal。

對(duì)于實(shí)現(xiàn)父子線程的傳參使用的一般就是InheritableThreadLocal,對(duì)于 InheritableThreadLocal 是如何實(shí)現(xiàn)的父子傳參可以參考之前發(fā)表的這篇文章。

有的同學(xué)就會(huì)問(wèn)了,既然有了InheritableThreadLocal能夠?qū)崿F(xiàn)父子線程的傳參,那么阿里為什么還要在開(kāi)源一個(gè)自己的TransmittableThreadLocal出來(lái)呢?

下面就說(shuō)一下TransmittableThreadLocal解決了什么問(wèn)題?

版本:TransmittableTreadLocal v2.14.5

代碼示例中都沒(méi)有做remove操作,實(shí)際使用中不要忘記哦。本文代碼示例加入remove方法不影響測(cè)試結(jié)果。

一、TransmittableThreadLocal解決了什么問(wèn)題?

先思考一個(gè)問(wèn)題,在業(yè)務(wù)開(kāi)發(fā)中,如果想異步執(zhí)行這個(gè)任務(wù)可以使用哪些方式?

  • 使用@Async注解
  • new Thread()
  • 線程池
  • MQ
  • 其它

上述的幾種方式中,暫時(shí)只探討線程的方式,MQ等其他方式暫不在本文的探討范圍內(nèi)。

不管是使用@Async注解,還是使用線程或者線程池,底層原理都是通過(guò)另一個(gè)子線程執(zhí)行的。

對(duì)于@Async注解原理不了解的點(diǎn)擊鏈接跳轉(zhuǎn)進(jìn)行查閱。

一文搞懂 @Async 注解原理

既然是子線程,那么在涉及到父子線程之間變量傳參的時(shí)候你們是通過(guò)什么方式實(shí)現(xiàn)的呢?

父子線程之間進(jìn)行變量的傳遞可以通過(guò)InheritableThreadLocal實(shí)現(xiàn)。

InheritableThreadLocal實(shí)現(xiàn)父子線程傳參的原理可以參考這篇。

InheritableThreadLocal 是如何實(shí)現(xiàn)的父子線程局部變量的傳遞

本文可以說(shuō)是對(duì)InheritableThreadLocal的一個(gè)補(bǔ)充。

當(dāng)我們?cè)谑褂胣ew Thread()時(shí),直接通過(guò)設(shè)置一個(gè)ThreadLocal即可實(shí)現(xiàn)變量的傳遞。

需要注意的是,此處傳值需要使用InheritableThreadLocal,因?yàn)門(mén)hreadLocal無(wú)法實(shí)現(xiàn)在子線程中獲取到父線程的值。

由于工作中大部分場(chǎng)景都是使用的線程池,所以我們上面的方式還可以生效嗎?

線程池中線程的數(shù)量是可以指定的,并且線程是由線程池創(chuàng)建好,池化之后反復(fù)使用的。所以此時(shí)的父子線程關(guān)系中的變量傳遞就沒(méi)有了意義,我們需要的是任務(wù)提交到線程池時(shí)的ThreadLocal變量值傳遞到任務(wù)執(zhí)行時(shí)的線程。

在InheritableThreadLocal原理這篇文章的末尾,我們提到了線程池的傳參方式,本質(zhì)上也是通過(guò)InheritableThreadLocal進(jìn)行的變量傳遞。

而阿里的TransmittableThreadLocal類是繼承加強(qiáng)的InheritableThreadLocal。

TransmittableThreadLocal可以解決線程池中復(fù)用線程時(shí),將值傳遞給實(shí)際執(zhí)行業(yè)務(wù)的線程,解決異步執(zhí)行時(shí)的上下文傳遞問(wèn)題。

除此之外,還有幾個(gè)典型場(chǎng)景例子:

  • 分布式跟蹤系統(tǒng)或者全鏈路壓測(cè)(鏈路打標(biāo))。
  • 日志收集系統(tǒng)上下文。
  • Session 級(jí) Cache。
  • 應(yīng)用容器或者上層框架跨應(yīng)用代碼給下層 SDK 傳遞信息。

二、TransmittableThreadLocal 怎么用?

上面我們知道了TransmittableThreadLocal可以用來(lái)做什么,解決的是線程池中池化線程復(fù)用線程時(shí)的值傳遞問(wèn)題。

下面我們就一起來(lái)看下怎么使用?

1.ThreadLocal

所有代碼示例都在 springboot 中演示。

ThreadLocal 在父子線程間是如法傳參的,使用方式如下:

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

    @RequestMapping("/set")
    public Object set(){
        stringThreadLocal.set("主線程給的值:stringThreadLocal");
        Thread thread = new Thread(() -> {
            System.out.println("讀取父線程stringThreadLocal的值:" + stringThreadLocal.get());
        });
        thread.start();
        return "";
    }
}

啟動(dòng)之后訪問(wèn) /test2/set,顯示如下:

通過(guò)上面的輸出可以看出來(lái),并沒(méi)有讀取到父線程的值。

所以為了實(shí)現(xiàn)父子傳參,需要把 ThreadLocal 修改為 InheritableThreadLocal 。

2.InheritableThreadLocal

代碼修改完成之后如下:

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    @RequestMapping("/set")
    public Object set(){
        stringThreadLocal.set("主線程給的值:stringThreadLocal");
        inheritableThreadLocal.set("主線程給的值:inheritableThreadLocal");
        Thread thread = new Thread(() -> {
            System.out.println("讀取父線程stringThreadLocal的值:" + stringThreadLocal.get());
            System.out.println("讀取父線程inheritableThreadLocal的值:" + inheritableThreadLocal.get());
        });
        thread.start();
        return "";
    }
}

同樣的執(zhí)行一下看輸出:

在上面的演示例子中,都是直接用的new Thread(),下面我們改為線程池的方式試試。

修改完成之后的代碼如下所示:

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    ThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());


    @RequestMapping("/set")
    public Object set(){
        for (int i = 0; i < 10; i++) {
            String val = "主線程給的值:inheritableThreadLocal:"+i;
            System.out.println("主線程set;"+val);
            inheritableThreadLocal.set(val);
            executor.execute(()->{
                System.out.println("線程池:讀取父線程 inheritableThreadLocal 的值:" + inheritableThreadLocal.get());
            });
        }
        return "";
    }
}

同樣的看下輸出:

通過(guò)輸出我們可以得出結(jié)論,當(dāng)使用線程池時(shí),因?yàn)榫€程都是復(fù)用的,在子線程中獲取父線程的值,可能獲取出來(lái)的是上一個(gè)線程 的值,所以這里會(huì)有線程安全問(wèn)題。

線程池中的線程并不一定每次都是新創(chuàng)建的,所以對(duì)于InheritableThreadLocal是無(wú)法實(shí)現(xiàn)父子傳參的。

如果感覺(jué)輸出不夠明顯可以輸出子線程的線程名稱。

下面我們看下怎么使用 TransmittableThreadLocal解決線程池中父子變量傳遞問(wèn)題。

3.TransmittableThreadLocal

繼續(xù)對(duì)上面代碼進(jìn)行改造,改造完成之后如下所示:

修改部分:TransmittableThreadLocal 的第一種使用方式,TtlRunnable.get() 封裝。

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @RequestMapping("/set")
    public Object set(){
        for (int i = 0; i < 10; i++) {
            String val = "主線程給的值:TransmittableThreadLocal:"+i;
            System.out.println("主線程set3;"+val);
            transmittableThreadLocal.set(val);
            executor.execute(TtlRunnable.get(()->{
                System.out.println("線程池線程:"+Thread.currentThread().getName()+
                        "讀取父線程 TransmittableThreadLocal 的值:"
                        + transmittableThreadLocal.get());
            }));
        }
        return "";
    }
}

執(zhí)行結(jié)果如下所示:

通過(guò)日志輸出可以看到,子線程的輸出已經(jīng)把父線程中設(shè)置的值全部輸出了,并沒(méi)有像 InheritableThreadLocal 那樣一直使用那幾個(gè)值。

可以得出結(jié)論,TransmittableThreadLocal可以解決線程池中復(fù)用線程時(shí),將值傳遞給實(shí)際執(zhí)行業(yè)務(wù)的線程,解決異步執(zhí)行時(shí)的上下文傳遞問(wèn)題。

那么這樣就沒(méi)問(wèn)題了嗎,看起來(lái)使用真的很簡(jiǎn)單,僅僅需要將 Runnable 封裝下即可,下面我們將ThreadLocal中存儲(chǔ)的 String 類型的值改為 Map在試試。

三、TransmittableThreadLocal 中的深拷貝

我們將 ThreadLocal 中存儲(chǔ)的值改為 Map,修改完代碼如下:

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<Map<String,Object>> transmittableThreadLocal = new TransmittableThreadLocal<>();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @RequestMapping("/set")
    public Object set(){
        Map<String, Object> map = new HashMap<>();
        map.put("mainThread","主線程給的值:main");
        System.out.println("主線程賦值:"+ map);
        transmittableThreadLocal.set(map);
        executor.execute(TtlRunnable.get(()->{
            System.out.println("線程池線程:"+Thread.currentThread().getName()+
                    "讀取父線程 TransmittableThreadLocal 的值:"
                    + transmittableThreadLocal.get());
        }));
        return "";
    }
}

調(diào)用接口執(zhí)行結(jié)果如下:

可以看到?jīng)]啥問(wèn)題,下面我們簡(jiǎn)單改一下代碼。

  • 在主線程提交子線程的任務(wù)之后再次修改 ThreadLocal 的值。
  • 在子線程中修改 ThreadLocal 的值。

修改完成的代碼如下所示:

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<Map<String, Object>> transmittableThreadLocal = new TransmittableThreadLocal<>();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @RequestMapping("/set")
    public Object set()  {
        Map<String, Object> map = transmittableThreadLocal.get();
        if (null == map) {map = new HashMap<>();}
        map.put("mainThread", "主線程給的值:main");
        System.out.println("主線程賦值:" + map);
        transmittableThreadLocal.set(map);
        executor.execute(TtlRunnable.get(() -> {
            System.out.println("子線程輸出:" + Thread.currentThread().getName() + "讀取父線程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
            Map<String, Object> childMap = transmittableThreadLocal.get();
            if (null == childMap){childMap = new HashMap<>();}
            childMap.put("childThread","子線程添加值");
        }));
        Map<String, Object> stringObjectMap = transmittableThreadLocal.get();
        if (null == stringObjectMap) {
            stringObjectMap = new HashMap<>();
        }
        stringObjectMap.put("mainThread-2", "主線程第二次賦值");
        transmittableThreadLocal.set(stringObjectMap);
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){e.printStackTrace();}
        System.out.println("主線程第二次輸出ThreadLocal:"+transmittableThreadLocal.get());
        return "";
    }
}

調(diào)用接口輸出如下:

通過(guò)日志輸出可以得出結(jié)論,當(dāng) ThreadLocal 存儲(chǔ)的是對(duì)象時(shí),父子線程共享同一個(gè)對(duì)象。

也就是說(shuō)父子線程之間的修改都是可見(jiàn)的,原因就是父子線程持有的 Map 都是同一個(gè),在父線程第二次設(shè)置值的時(shí)候,因?yàn)樾薷牡亩际峭粋€(gè) Map,所以子線程也可以讀取到。

這一點(diǎn)需要特別的注意,如果有嚴(yán)格的業(yè)務(wù)邏輯,且共享同一個(gè)ThreadLocal,需要注意這個(gè)線程安全問(wèn)題。

那么怎么解決呢,那就是深拷貝,對(duì)象的深拷貝,保證父子線程獨(dú)立,在修改的時(shí)候就不會(huì)出現(xiàn)父子線程共享同一個(gè)對(duì)象的事情。

TransmittableThreadLocal 其中有一個(gè) copy 方法,copy 方法就是復(fù)制父線程值的,在此處返回一個(gè)新的對(duì)象,而不是父線程的對(duì)象即可,代碼修改如下:

為什么是 copy 方法,后文會(huì)有介紹。

@RestController
@RequestMapping("/test2")
public class Test2Controller {

    ThreadLocal<Map<String, Object>> transmittableThreadLocal = new TransmittableThreadLocal(){
        @Override
        public Object copy(Object parentValue) {
            return new HashMap<>((Map)parentValue);
        }
    };
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
    @RequestMapping("/set")
    public Object set()  {
        Map<String, Object> map = transmittableThreadLocal.get();
        if (null == map) {map = new HashMap<>();}
        map.put("mainThread", "主線程給的值:main");
        System.out.println("主線程賦值:" + map);
        transmittableThreadLocal.set(map);
        executor.execute(TtlRunnable.get(() -> {
            System.out.println("子線程輸出:" + Thread.currentThread().getName() + "讀取父線程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
            Map<String, Object> childMap = transmittableThreadLocal.get();
            if (null == childMap){childMap = new HashMap<>();}
            childMap.put("childThread","子線程添加值");
        }));
        Map<String, Object> stringObjectMap = transmittableThreadLocal.get();
        if (null == stringObjectMap) {
            stringObjectMap = new HashMap<>();
        }
        stringObjectMap.put("mainThread-2", "主線程第二次賦值");
        transmittableThreadLocal.set(stringObjectMap);
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){e.printStackTrace();}
        System.out.println("主線程第二次輸出ThreadLocal:"+transmittableThreadLocal.get());
        return "";
    }
}

修改部分如下:

調(diào)用接口,查看執(zhí)行結(jié)果可以發(fā)現(xiàn),父子線程的修改已經(jīng)是獨(dú)立的對(duì)象在修改,不再是共享的。

相信到了這,對(duì)于 TransmittableThreadLocal 如何使用應(yīng)該會(huì)了吧,下面我們就一起來(lái)看下 TransmittableThreadLocal到底是如何做到的父子線程變量的傳遞的。

四、TransmittableThreadLocal 原理

TransmittableThreadLocal 簡(jiǎn)稱 TTL。

在開(kāi)始之前先放一張官方的時(shí)序圖,結(jié)合圖看源碼更容易懂哦!

1.TransmittableThreadLocal 使用方式

(1) 修飾 Runnable 和Callable

這種方式就是上面代碼示例中的形式,通過(guò) TtlRunnable和TtlCallable 修改傳入線程池的 Runnable 和 Callable。

(2) 修飾線程池

修飾線程池可以使用TtlExecutors工具類實(shí)現(xiàn),其中有如下方法可以使用。

(3) Java Agent

Agent 的形式不會(huì)對(duì)代碼入侵,具體的使用可以參考官網(wǎng),這里就不再說(shuō)了,官網(wǎng)鏈接我會(huì)放在文章末尾。

需要注意的是,如果需要和其他 Agent (如Skywalking、Promethues)一起使用,需要把 TransmittableThreadLocal Java Agent 放在第一位。

2.源碼分析

先簡(jiǎn)單的概括下:

  • 修飾 Runnable ,將主線程的 TTL 值傳入到 TtlRunnable 的構(gòu)造方法中。
  • 將子線程的 TTL 進(jìn)行備份,主線程的值設(shè)置到子線程中。
  • 子線程執(zhí)行業(yè)務(wù)邏輯。
  • 刪除子線程新增的 TTL,將備份重新設(shè)置到子線程中。

(1) TtlRunnable#run 方法做了什么

先從TtlRunnable#run方法入手。

從整體流程來(lái)看,整個(gè)上下文的傳遞流程可以規(guī)范成快照、回放、恢復(fù)(CRR)三個(gè)操作。

  • captured 是主線程(線程A)傳遞的 TTL的值。
  • backup 是子線程(線程B)中當(dāng)前存在的 TTL 的值。
  • replay 操作會(huì)將主線程中(線程A)的 TTL 的值回放到當(dāng)前子線程(線程B)中,并返回回放前的 TTL 值的備份也就是上面的 backup。
  • runnable.run() 是待執(zhí)行的方法。
  • restore 是恢復(fù)子線程(線程B)進(jìn)入之時(shí)備份的 TTL 的值。因?yàn)樽泳€程的 TTL 可能已經(jīng)發(fā)生變化,所以該方法就是回滾到子線程執(zhí)行 replay 方法之前的 TTL 值。

(2) captured 快照是什么時(shí)候做的

同學(xué)們思考下,快照又是什么時(shí)候做的呢?

通過(guò)上面 run 方法可以看到,在該方法的第一行已經(jīng)是獲取快照的值了,所以生成快照肯定不在run方法內(nèi)了。

提示一下,開(kāi)頭放的時(shí)序圖還記得嗎,可以看下4.1。

還記得我們封裝了線程嗎,使用TtlRunnable.get()進(jìn)行封裝的,返回的是TtlRunnable。

答案就在這個(gè)方法內(nèi)部,來(lái)看下方法內(nèi)部做了哪些事情。

@Nullable
    @Contract(value = "null -> null; !null -> !null", pure = true)
    public static TtlRunnable get(@Nullable Runnable runnable) {
        return get(runnable, false, false);
    }

    @Nullable
    @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true)
    public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
        if (runnable == null) 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);
    }

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

可以看到在調(diào)用TtlRunnable.get() 方法的最后,調(diào)用了TtlRunnable的構(gòu)造方法,在該方法內(nèi)部,又調(diào)用了capture方法。

capture 方法內(nèi)部是真正做快照的地方。

其中的transmittee.capture()調(diào)用的ttlTransmittee的。

需要注意的是,threadLocal.copyValue()拷貝的是引用,所以如果是對(duì)象,就需要重寫(xiě)copy方法。

public T copy(T parentValue) {
    return parentValue;
}

代碼中的 holder 是一個(gè)InheritableThreadLocal,他的值類型是WeakHashMap。

key 是TransmittableThreadLocal,value 始終是 null且始終沒(méi)有使用。

里面維護(hù)了所有使用到的 TransmittableThreadLocal,統(tǒng)一添加到 holder中。

到了這又有了一個(gè)疑問(wèn)?holder 中的 值什么時(shí)候添加的?

陷入看源碼的誤區(qū),一個(gè)一個(gè)的來(lái),不要一個(gè)方法一直擴(kuò)散,要有一條主線,對(duì)于我們這里,已經(jīng)知道了什么時(shí)候進(jìn)行的快照,如何快照的就可以了,對(duì)于 holder中的值在哪里添加的,這就是另一個(gè)問(wèn)題了。

(3) holder 中在哪賦值的

holder 中賦值的地方在 addThisToHolder方法中實(shí)現(xiàn)。

具體可以在transmittableThreadLocal.get()與transmittableThreadLocal.set()中查看。

@Override
    public final T get() {
        T value = super.get();
        if (disableIgnoreNullValueSemantics || value != null) addThisToHolder();
        return value;
    }   
@Override
    public final void set(T value) {
        if (!disableIgnoreNullValueSemantics && value == null) {
            // may set null to remove value
            remove();
        } else {
            super.set(value);
            addThisToHolder();
        }
    }

    private void addThisToHolder() {
        if (!holder.get().containsKey(this)) {
            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
        }
    }

addThisToHolder 中將此 TransmittableThreadLocal實(shí)例添加到 holder 的 key 中。

通過(guò)此方法,可以將所有用到的 TransmittableThreadLocal 實(shí)例記錄。

(4) replay 備份與回放數(shù)據(jù)

replay方法只做了兩件事。

  • 將快照中(主線程傳遞)的數(shù)據(jù)設(shè)置到當(dāng)前子線程中。
  • 返回當(dāng)前線程的 TTL 值(快照回放當(dāng)前子線程之前的TTL)。

在 transmittee.replay 方法中真正的執(zhí)行了備份與回放操作。

(5) restore 恢復(fù)

我們看下 CRR 操作的最后一步 restore 恢復(fù)。

restore 的功能就是將當(dāng)前線程的 TTL 恢復(fù)到方法執(zhí)行前備份的值。

restore 方法內(nèi)部調(diào)用了transmittee.restore方法。

思考一下:為什么要在任務(wù)執(zhí)行結(jié)束之后執(zhí)行 restore 操作呢?

首先就是為了保持線程的干凈,線程池中的線程都是復(fù)用的。

當(dāng)一個(gè)線程重復(fù)執(zhí)行多個(gè)任務(wù)的時(shí)候,第一個(gè)任務(wù)修改了 TTL 的值,如果不進(jìn)行 restore ,第二個(gè)任務(wù)開(kāi)始時(shí)就會(huì)獲取到第一個(gè)任務(wù)修改之后的值,而不是預(yù)期的初始的值。

五、TransmittableThreadLocal的初始化方法

對(duì)于TransmittableThreadLocal相關(guān)的初始化方法有三個(gè),如圖所示。

1.ThreadLocal#initialValue()

ThreadLocal 沒(méi)有值時(shí)取值的方法,該方法在ThreadLocal#get 觸發(fā)。

需要注意的是ThreadLocal#initialValue()是懶加載的,也就是創(chuàng)建ThreadLocal實(shí)例的時(shí)候并不會(huì)觸發(fā)ThreadLocal#initialValue()的調(diào)用。

如果我們先進(jìn)行了 ThreadLocal.set(T)操作,在進(jìn)行取值操作,也不會(huì)觸發(fā)ThreadLocal#initialValue(),因?yàn)橐呀?jīng)有值了,即使是設(shè)置的NULL也不會(huì)觸發(fā)該初始化操作。

如果調(diào)用了remove 方法,在取值會(huì)觸發(fā)初始化ThreadLocal#initialValue()操作。

2.InheritableThreadLocal#childValue(T)

childValue方法用于在創(chuàng)建新線程時(shí),初始化子線程的InheritableThreadLocal值。

3.TransmittableThreadLocal#copy(T)

在TtlRunnable或者TtlCallable 創(chuàng)建的時(shí)候觸發(fā)。

例如 TtlRunnable.get()快照時(shí)觸發(fā)。

用于初始化在例如:TtlRunnable執(zhí)行中的TransmittableThreadLocal值。

六、總結(jié)

本文通過(guò)代碼示例依次演示ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal實(shí)現(xiàn)父子線程傳參演化過(guò)程。

得出結(jié)論如下:

  • 使用ThreadLocal無(wú)法實(shí)現(xiàn)父子線程傳參。
  • InheritableThreadLocal可以實(shí)現(xiàn)父子傳參,但是線程池場(chǎng)景復(fù)用線程問(wèn)題無(wú)法解決。
  • TransmittableThreadLocal可以解決線程池復(fù)用線程的問(wèn)題。

需要注意的是TransmittableThreadLocal保存對(duì)象時(shí)有深拷貝需求的需要重寫(xiě)TransmittableThreadLocal#copy(T)方法。

責(zé)任編輯:趙寧寧 來(lái)源: 醉魚(yú)Java
相關(guān)推薦

2021-07-08 10:08:03

DvaJS前端Dva

2019-11-06 17:30:57

cookiesessionWeb

2020-12-07 06:19:50

監(jiān)控前端用戶

2020-03-18 14:00:47

MySQL分區(qū)數(shù)據(jù)庫(kù)

2021-06-30 08:45:02

內(nèi)存管理面試

2022-06-07 10:13:22

前端沙箱對(duì)象

2023-09-08 08:20:46

ThreadLoca多線程工具

2023-11-23 06:50:08

括號(hào)

2024-07-12 14:46:20

2021-01-13 05:21:59

參數(shù)

2022-04-11 10:56:43

線程安全

2024-08-08 14:57:32

2021-07-21 05:24:32

EventBus3.0Android單例模式

2023-01-27 18:55:37

Python內(nèi)置函數(shù)

2023-12-15 15:55:24

Linux線程同步

2023-09-22 10:45:47

云原生云計(jì)算

2021-10-20 08:49:30

Vuexvue.js狀態(tài)管理模式

2020-12-18 09:36:01

JSONP跨域面試官

2023-04-12 08:38:44

函數(shù)參數(shù)Context

2021-08-05 06:54:05

觀察者訂閱設(shè)計(jì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)