欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

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

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

但是在真實的項目當中,異步操作幾乎都是用的線程池來處理,也就意味著線程是復用的,這就導致了不同任務的上下文使用的是同一個線程的上下文,這就會導致程序出現(xiàn)意料不到的BUG。

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

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();
      }
    }
  }

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

示例

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

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

1.我們準備了只有一個線程的線程池,主要測試線程復用的情況;

2.準備了兩個任務,第一個任務檢查是否能夠拿到正確的上下文數(shù)據(jù);第二個任務測試是否因為第一個任務修改上下文受到影響;

執(zhí)行結果如下:

pool-1-thread-1開始
第一個任務執(zhí)行結果:china
pool-1-thread-1結束
pool-1-thread-1開始
第二個任務執(zhí)行結果:china
pool-1-thread-1結束
主線程執(zhí)行結果:china

通過上述示例,我們可以得出以下結論:

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

2.TransmittableThreadLocal解決了線程復用導致多任務共享同一個線程上下文的問題;

使用方式

包裝任務

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

包裝線程池

為了我們在使用線程池時,不用每次都使用TtlRunnableTtlCallable來包裝所有任務,TransmittableThreadLocal還提供了包裝線程池的方法:

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

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

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

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

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

小結

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

1.針對任務Runnable、Callable實例,使用TtlRunnable.get()、TtlCallable.get()包裝;

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

以上就是TransmittableThreadLocal解決線程間上下文傳遞煩惱的詳細內容,更多關于TransmittableThreadLocal線程傳遞的資料請關注腳本之家其它相關文章!

相關文章

  • Java 基礎全面講解StringBuffer類的使用

    Java 基礎全面講解StringBuffer類的使用

    當對字符串進行修改的時候,需要使用 StringBuffer 和 StringBuilder類,和String類不同的是,StringBuffer和 StringBuilder類的對象能夠被多次的修改,并且不產生新的未使用對象
    2022-01-01
  • SpringBoot下載Excel文件時,報錯文件損壞的解決方案

    SpringBoot下載Excel文件時,報錯文件損壞的解決方案

    這篇文章主要介紹了SpringBoot下載Excel文件時,報錯文件損壞的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • 一次mybatis連接查詢遇到的坑實戰(zhàn)記錄

    一次mybatis連接查詢遇到的坑實戰(zhàn)記錄

    這篇文章主要給大家介紹了關于一次mybatis連接查詢遇到的坑的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • java-thymeleaf的使用方式

    java-thymeleaf的使用方式

    這篇文章主要介紹了java-thymeleaf的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • 關于SpringMVC對Restful風格的支持詳解

    關于SpringMVC對Restful風格的支持詳解

    Restful就是一個資源定位及資源操作的風格,不是標準也不是協(xié)議,只是一種風格,是對http協(xié)議的詮釋,下面這篇文章主要給大家介紹了關于SpringMVC對Restful風格支持的相關資料,需要的朋友可以參考下
    2022-01-01
  • maven引入mysql-connector-java包失敗的解決方案

    maven引入mysql-connector-java包失敗的解決方案

    這篇文章主要介紹了maven引入mysql-connector-java包失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • JAVA DOM解析XML文件過程詳解

    JAVA DOM解析XML文件過程詳解

    這篇文章主要介紹了JAVA DOM解析XML文件過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • 基于java springboot + mybatis實現(xiàn)電影售票管理系統(tǒng)

    基于java springboot + mybatis實現(xiàn)電影售票管理系統(tǒng)

    這篇文章主要介紹了基于java springboot + mybatis實現(xiàn)的完整電影售票管理系統(tǒng)基于java springboot + mybatis,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • 淺析Java中的動態(tài)代理

    淺析Java中的動態(tài)代理

    動態(tài)代理指代理類和目標類的關系在程序運行的時候確定的,客戶通過代理類來調用目標對象的方法。本文將通過案例詳細講解一下Java動態(tài)代理的原理及實現(xiàn),需要的可以參考一下
    2022-09-09
  • Java中字符串替換的四種方法舉例總結

    Java中字符串替換的四種方法舉例總結

    Java提供了多種方法來替換字符串,其中最常用的是使用replace()方法和正則表達式,下面這篇文章主要給大家介紹了關于Java中字符串替換的四種方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-08-08

最新評論