最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

TransmittableThreadLocal解決線程間上下文傳遞煩惱

 更新時(shí)間:2022年11月29日 10:06:38   作者:夢(mèng)想實(shí)現(xiàn)家_Z  
這篇文章主要為大家介紹了TransmittableThreadLocal解決線程間上下文傳遞煩惱詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在一些項(xiàng)目中,經(jīng)常會(huì)遇到需要把當(dāng)前線程中的上下文傳遞到其他線程中的情況,比如某項(xiàng)目包含國(guó)際化操作,在業(yè)務(wù)請(qǐng)求進(jìn)來(lái)時(shí)需要把對(duì)應(yīng)的國(guó)家代碼存儲(chǔ)到當(dāng)前線程中,以便后續(xù)的業(yè)務(wù)邏輯能夠根據(jù)國(guó)家代碼正確地處理;另外在一些異步化操作中,也要保證異常線程中也能夠正確地獲取到對(duì)應(yīng)的國(guó)家代碼。

在上述業(yè)務(wù)場(chǎng)景中,我們很自然的就想到了使用ThreadLocal,但是ThreadLocal無(wú)法解決父子線程間上下文傳遞的問(wèn)題,此時(shí)InheritableThreadLocal站出來(lái)了,它在創(chuàng)建子線程的過(guò)程中

拷貝了父親線程中的inheritableThreadLocals數(shù)據(jù),在new Thread()代碼中,有一段這樣的代碼:

Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

但是在真實(shí)的項(xiàng)目當(dāng)中,異步操作幾乎都是用的線程池來(lái)處理,也就意味著線程是復(fù)用的,這就導(dǎo)致了不同任務(wù)的上下文使用的是同一個(gè)線程的上下文,這就會(huì)導(dǎo)致程序出現(xiàn)意料不到的BUG。

針對(duì)這種情況,我們發(fā)現(xiàn)應(yīng)該把線程上下文轉(zhuǎn)變成任務(wù)上下文,這樣的話才能避免多個(gè)任務(wù)共用一個(gè)線程上下文,為此我們不得不封裝一下每一個(gè)傳入線程池的任務(wù):

class RunnableWrap implements Runnable {
    private ThreadLocal threadLocal;
    private Object context;
    private Runnable task;
    public RunnableWrap(ThreadLocal threadLocal, Runnable task) {
      this.threadLocal = threadLocal;
      this.context = threadLocal.get();
      this.task = task;
    }
    @Override
    public void run() {
      try {
        threadLocal.set(context);
        task.run();
      } finally {
        threadLocal.remove();
      }
    }
  }

但是這樣做確實(shí)不是很優(yōu)雅,所以為何不用TransmittableThreadLocal試試呢?

示例

我們來(lái)通過(guò)一個(gè)示例演示一下TransmittableThreadLocal是否能夠在線程池中實(shí)現(xiàn)上下文的傳遞,并且滿足任務(wù)間上下文的隔離效果:

private static TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();
// 使用只有一個(gè)線程的線程池,測(cè)試線程復(fù)用是否影響TransmittableThreadLocal的效果
private static final Executor EXECUTOR = Executors.newFixedThreadPool(1);
  public static void main(String[] args) throws InterruptedException {
    // 設(shè)置主線程的上下文為"china"
    CONTEXT.set("china");
    // 創(chuàng)建第一個(gè)任務(wù),通過(guò)TtlRunnable.get()包裝;
    // 在第一個(gè)任務(wù)中查看上下文數(shù)據(jù),檢查是否拿到正確的上下文;
    // 另外再修改掉該上下文,主要測(cè)試是否會(huì)影響第二個(gè)任務(wù)的上下文;
    Runnable task1 = TtlRunnable.get(() -> {
      Thread thread = Thread.currentThread();
      System.out.println(thread.getName() + "開(kāi)始");
      String countryCode = CONTEXT.get();
      System.out.println("第一個(gè)任務(wù)執(zhí)行結(jié)果:" + countryCode);
      // 修改該線程中上下文值,檢查是否影響第二個(gè)任務(wù)
      CONTEXT.set("US");
      System.out.println(thread.getName() + "結(jié)束");
    });
    // 第二個(gè)任務(wù)主要測(cè)試上下文是否受第一個(gè)任務(wù)的影響
    Runnable task2 = TtlRunnable.get(() -> {
      Thread thread = Thread.currentThread();
      System.out.println(thread.getName() + "開(kāi)始");
      String countryCode = CONTEXT.get();
      System.out.println("第二個(gè)任務(wù)執(zhí)行結(jié)果:" + countryCode);
      System.out.println(thread.getName() + "結(jié)束");
    });
    // 按順序執(zhí)行兩個(gè)任務(wù),全部放到線程池中執(zhí)行
    CompletableFuture.runAsync(task1, EXECUTOR1)
        .thenRunAsync(task2, EXECUTOR1);
    // 檢查主線程上下文是否受影響;
    String countryCode = CONTEXT.get();
    System.out.println("主線程執(zhí)行結(jié)果:" + countryCode);
    Thread.sleep(10000);
  }

1.我們準(zhǔn)備了只有一個(gè)線程的線程池,主要測(cè)試線程復(fù)用的情況;

2.準(zhǔn)備了兩個(gè)任務(wù),第一個(gè)任務(wù)檢查是否能夠拿到正確的上下文數(shù)據(jù);第二個(gè)任務(wù)測(cè)試是否因?yàn)榈谝粋€(gè)任務(wù)修改上下文受到影響;

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

pool-1-thread-1開(kāi)始
第一個(gè)任務(wù)執(zhí)行結(jié)果:china
pool-1-thread-1結(jié)束
pool-1-thread-1開(kāi)始
第二個(gè)任務(wù)執(zhí)行結(jié)果:china
pool-1-thread-1結(jié)束
主線程執(zhí)行結(jié)果:china

通過(guò)上述示例,我們可以得出以下結(jié)論:

1.TransmittableThreadLocal可以讓線程池中的上下文保持和父線程一致;

2.TransmittableThreadLocal解決了線程復(fù)用導(dǎo)致多任務(wù)共享同一個(gè)線程上下文的問(wèn)題;

使用方式

包裝任務(wù)

  • 通過(guò)上述示例,我們學(xué)到了最基本的一種使用方式:TtlRunnable.get(),它可以用來(lái)包裝Runnable接口的所有實(shí)例;
  • 同樣的,針對(duì)Callable下的實(shí)例,我們可以使用TtlCallable.get()來(lái)包裝

包裝線程池

為了我們?cè)谑褂镁€程池時(shí),不用每次都使用TtlRunnableTtlCallable來(lái)包裝所有任務(wù),TransmittableThreadLocal還提供了包裝線程池的方法:

TtlExecutors.getTtlExecutor(Executors.newFixedThreadPool(1));

通過(guò)包裝好的線程池,我們可以修改一下上面的示例代碼:

private static TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();
// 使用只有一個(gè)線程的線程池,測(cè)試線程復(fù)用是否影響TransmittableThreadLocal的效果
private static final Executor EXECUTOR = TtlExecutors.getTtlExecutor(Executors.newFixedThreadPool(1));
  public static void main(String[] args) throws InterruptedException {
    // 設(shè)置主線程的上下文為"china"
    CONTEXT.set("china");
    // 創(chuàng)建第一個(gè)任務(wù),通過(guò)TtlRunnable.get()包裝;
    // 在第一個(gè)任務(wù)中查看上下文數(shù)據(jù),檢查是否拿到正確的上下文;
    // 另外再修改掉該上下文,主要測(cè)試是否會(huì)影響第二個(gè)任務(wù)的上下文;
    Runnable task1 = () -> {
      Thread thread = Thread.currentThread();
      System.out.println(thread.getName() + "開(kāi)始");
      String countryCode = CONTEXT.get();
      System.out.println("第一個(gè)任務(wù)執(zhí)行結(jié)果:" + countryCode);
      // 修改該線程中上下文值,檢查是否影響第二個(gè)任務(wù)
      CONTEXT.set("US");
      System.out.println(thread.getName() + "結(jié)束");
    };
    // 第二個(gè)任務(wù)主要測(cè)試上下文是否受第一個(gè)任務(wù)的影響
    Runnable task2 = () -> {
      Thread thread = Thread.currentThread();
      System.out.println(thread.getName() + "開(kāi)始");
      String countryCode = CONTEXT.get();
      System.out.println("第二個(gè)任務(wù)執(zhí)行結(jié)果:" + countryCode);
      System.out.println(thread.getName() + "結(jié)束");
    };
    // 按順序執(zhí)行兩個(gè)任務(wù),全部放到線程池中執(zhí)行
    CompletableFuture.runAsync(task1, EXECUTOR1)
        .thenRunAsync(task2, EXECUTOR1);
    // 檢查主線程上下文是否受影響;
    String countryCode = CONTEXT.get();
    System.out.println("主線程執(zhí)行結(jié)果:" + countryCode);
    Thread.sleep(10000);
  }

1.可以看出,我們包裝好線程池后,就不再需要包裝任務(wù)了,所有的任務(wù)都不需要TtlRunnable.get();

2.從包裝好的線程池中我們可以發(fā)現(xiàn),返回的實(shí)例其實(shí)是ExecutorTtlWrapper對(duì)象,里面的submit方法、execute()方法上把傳進(jìn)去Runnable參數(shù)使用TtlRunnable.get()做了一層包裝;

小結(jié)

本文從業(yè)務(wù)角度切入,通過(guò)層層遞進(jìn)的方式從ThreadLocalInheritableThreadLocal在業(yè)務(wù)上的應(yīng)用及產(chǎn)生的相關(guān)問(wèn)題點(diǎn),逐步引出TransmittableThreadLocal,通過(guò)示例的方式驗(yàn)證TransmittableThreadLocal符合我們的需求,并且了解了TransmittableThreadLocal針對(duì)任務(wù)及線程池的使用方式:

1.針對(duì)任務(wù)RunnableCallable實(shí)例,使用TtlRunnable.get()、TtlCallable.get()包裝;

2.針對(duì)線程池,使用TtlExecutors.getTtlExecutor()包裝;

以上就是TransmittableThreadLocal解決線程間上下文傳遞煩惱的詳細(xì)內(nèi)容,更多關(guān)于TransmittableThreadLocal線程傳遞的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java8新日期時(shí)間API的20個(gè)使用示例

    Java8新日期時(shí)間API的20個(gè)使用示例

    這篇文章主要介紹了Java8新日期時(shí)間API的20個(gè)使用示例,為了學(xué)習(xí)Java 8的這個(gè)新庫(kù),這里我創(chuàng)建了20個(gè)以任務(wù)為導(dǎo)向的例子,需要的朋友可以參考下
    2015-03-03
  • SpringBoot ApplicationEvent之事件發(fā)布與監(jiān)聽(tīng)機(jī)制詳解

    SpringBoot ApplicationEvent之事件發(fā)布與監(jiān)聽(tīng)機(jī)制詳解

    本文將深入探討SpringBoot的事件機(jī)制,介紹其核心概念、實(shí)現(xiàn)方法及最佳實(shí)踐,幫助開(kāi)發(fā)者構(gòu)建更加靈活、可維護(hù)的應(yīng)用架構(gòu),希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Apache Calcite進(jìn)行SQL解析(java代碼實(shí)例)

    Apache Calcite進(jìn)行SQL解析(java代碼實(shí)例)

    Calcite是一款開(kāi)源SQL解析工具, 可以將各種SQL語(yǔ)句解析成抽象語(yǔ)法樹AST(Abstract Syntax Tree), 之后通過(guò)操作AST就可以把SQL中所要表達(dá)的算法與關(guān)系體現(xiàn)在具體代碼之中,今天通過(guò)代碼實(shí)例給大家介紹Apache Calcite進(jìn)行SQL解析問(wèn)題,感興趣的朋友一起看看吧
    2022-01-01
  • SpringBoot?+?Disruptor實(shí)現(xiàn)特快高并發(fā)處理及使用Disruptor高速實(shí)現(xiàn)隊(duì)列的過(guò)程

    SpringBoot?+?Disruptor實(shí)現(xiàn)特快高并發(fā)處理及使用Disruptor高速實(shí)現(xiàn)隊(duì)列的過(guò)程

    Disruptor是一個(gè)開(kāi)源的Java框架,它被設(shè)計(jì)用于在生產(chǎn)者—消費(fèi)者(producer-consumer problem,簡(jiǎn)稱PCP)問(wèn)題上獲得盡量高的吞吐量(TPS)和盡量低的延遲,這篇文章主要介紹了SpringBoot?+?Disruptor?實(shí)現(xiàn)特快高并發(fā)處理,使用Disruptor高速實(shí)現(xiàn)隊(duì)列,需要的朋友可以參考下
    2023-11-11
  • Java使用ProcessBuilder?API優(yōu)化流程

    Java使用ProcessBuilder?API優(yōu)化流程

    Java?的?Process?API?為開(kāi)發(fā)者提供了執(zhí)行操作系統(tǒng)命令的強(qiáng)大功能,這篇文章將詳細(xì)介紹如何使用?ProcessBuilder?API?來(lái)方便的操作系統(tǒng)命令,需要的可以收藏一下
    2023-06-06
  • SpringBoot?多環(huán)境打包最佳實(shí)踐記錄

    SpringBoot?多環(huán)境打包最佳實(shí)踐記錄

    SpringBoot通過(guò)配置多環(huán)境文件和在打包時(shí)指定激活的環(huán)境,實(shí)現(xiàn)多環(huán)境打包與部署,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎

    Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎

    這篇文章主要介紹了Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • SpringBoot中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移的詳細(xì)流程

    SpringBoot中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移的詳細(xì)流程

    本文介紹了如何在Spring Boot項(xiàng)目中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移,Flyway通過(guò)SQL腳本管理數(shù)據(jù)庫(kù)變更,支持自動(dòng)執(zhí)行和版本控制,避免了手動(dòng)執(zhí)行SQL腳本的錯(cuò)誤和維護(hù)困難,需要的朋友可以參考下
    2025-02-02
  • 詳解Java如何實(shí)現(xiàn)在PDF中插入,替換或刪除圖像

    詳解Java如何實(shí)現(xiàn)在PDF中插入,替換或刪除圖像

    圖文并茂的內(nèi)容往往讓人看起來(lái)更加舒服,如果只是文字內(nèi)容的累加,往往會(huì)使讀者產(chǎn)生視覺(jué)疲勞。搭配精美的文章配圖則會(huì)使文章內(nèi)容更加豐富。那我們要如何在PDF中插入、替換或刪除圖像呢?別擔(dān)心,今天為大家介紹一種高效便捷的方法
    2023-01-01
  • mybatis中如何傳遞單個(gè)String類型的參數(shù)

    mybatis中如何傳遞單個(gè)String類型的參數(shù)

    這篇文章主要介紹了mybatis中如何傳遞單個(gè)String類型的參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評(píng)論

乐安县| 玉环县| 临武县| 新干县| 张家界市| 通河县| 怀来县| 福建省| 南召县| 布拖县| 襄汾县| 晋州市| 新郑市| 邢台县| 本溪| 荆州市| 杭锦后旗| 泰顺县| 长治县| 金堂县| 黑河市| 广州市| 札达县| 宁波市| 尼木县| 莫力| 辽中县| 和龙市| 九龙坡区| 恭城| 延安市| 同德县| 丽江市| 特克斯县| 惠安县| 芜湖县| 嘉兴市| 乐都县| 炉霍县| 阿图什市| 肃宁县|