阿里開源支持緩存線程池的ThreadLocal Transmittable ThreadLocal(TTL)


功能

在使用線程池等會緩存線程的組件情況下,提供ThreadLocal值的傳遞功能。

JDK的InheritableThreadLocal類可以完成父子線程值的傳遞。 但對於使用線程池等會緩存線程的組件的情況,線程由線程池創建好,並且線程是緩存起來反復使用的;這時父子線程關系的上下文傳遞已經沒有意義,應用中要做上下文傳遞,實際上是在把 任務提交給線程池時的上下文傳遞到 任務執行時。

本庫提供的TransmittableThreadLocal類繼承並加強InheritableThreadLocal類,解決上述的問題,使用詳見User Guide

歡迎

需求場景

在ThreadLocal的需求場景即是TTL的潛在需求場景,如果你的業務需要『在使用線程池等會緩存線程的組件情況下傳遞ThreadLocal』則是TTL目標場景。

下面是幾個典型場景例子。

  1. 分布式跟蹤系統

  2. 應用容器或上層框架跨應用代碼給下層SDK傳遞信息

  3. 日志收集記錄系統上下文

各個場景的展開說明參見子文檔 需求場景

User Guide

使用類TransmittableThreadLocal來保存上下文,並跨線程池傳遞。

TransmittableThreadLocal繼承InheritableThreadLocal,使用方式也類似。

InheritableThreadLocal,添加了protected方法copy,用於定制 任務提交給線程池時的上下文傳遞到 任務執行時時的拷貝行為,缺省是傳遞的是引用。

具體使用方式見下面的說明。

1. 簡單使用

父線程給子線程傳遞值。

示例代碼:

// 在父線程中設置 
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // =====================================================  // 在子線程中可以讀取, 值是"value-set-in-parent"  String value = parent.get();

這是其實是InheritableThreadLocal的功能,應該使用InheritableThreadLocal來完成。

但對於使用了異步執行(往往使用線程池完成)的情況,線程由線程池創建好,並且線程是緩存起來反復使用的。

這時父子線程關系的上下文傳遞已經沒有意義,應用中要做上下文傳遞,實際上是在把 任務提交給線程池時的上下文傳遞到任務執行時。 解決方法參見下面的這幾種用法。

2. 保證線程池中傳遞值

2.1 修飾Runnable和Callable

使用com.alibaba.ttl.TtlRunnablecom.alibaba.ttl.TtlCallable來修飾傳入線程池的Runnable和Callable。

示例代碼:

TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent"); Runnable task = new Task("1"); // 額外的處理,生成修飾了的對象 ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task);  executorService.submit(ttlRunnable); // =====================================================  // Task中可以讀取, 值是"value-set-in-parent" String value = parent.get();

上面演示了Runnable,Callable的處理類似

TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Callable call = new Call("1"); // 額外的處理,生成修飾了的對象 ttlCallable Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // =====================================================  // Call中可以讀取, 值是"value-set-in-parent" String value = parent.get();

整個過程的完整時序圖

時序圖

2.2 修飾線程池

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

通過工具類com.alibaba.ttl.threadpool.TtlExecutors完成,有下面的方法:

  • getTtlExecutor:修飾接口Executor

  • getTtlExecutorService:修飾接口ExecutorService

  • ScheduledExecutorService:修飾接口ScheduledExecutorService

示例代碼:

ExecutorService executorService = ... // 額外的處理,生成修飾了的對象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Runnable task = new Task("1"); Callable call = new Call("2"); executorService.submit(task); executorService.submit(call); // =====================================================  // Task或是Call中可以讀取, 值是"value-set-in-parent" String value = parent.get();

2.3 使用Java Agent來修飾JDK線程池實現類

這種方式,實現線程池的傳遞是透明的,代碼中沒有修飾Runnable或是線程池的代碼。
# 即可以做到應用代碼 無侵入,后面文檔有結合實際場景的架構對這一點的說明。

示例代碼:

// 框架代碼 
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // 應用代碼  ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new Task("1"); Callable call = new Call("2"); executorService.submit(task); executorService.submit(call); // =====================================================  // Task或是Call中可以讀取, 值是"value-set-in-parent"  String value = parent.get();

Demo參見AgentDemo.java

目前Agent中,修飾了jdk中的兩個線程池實現類(實現代碼在TtlTransformer.java):

  • java.util.concurrent.ThreadPoolExecutor

  • java.util.concurrent.ScheduledThreadPoolExecutor

在Java的啟動參數加上:

  • -Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.x.jar

  • -javaagent:/path/to/transmittable-thread-local-2.x.x.jar

注意:

  • Agent修改是JDK的類,類中加入了引用TTL的代碼,所以TTL Agent的Jar要加到bootclasspath上。

Java命令行示例如下:

java -Xbootclasspath/a:transmittable-thread-local-2.0.0.jar \     -javaagent:transmittable-thread-local-2.0.0.jar \     -cp classes \     com.alibaba.ttl.threadpool.agent.demo.AgentDemo

有Demo演示『使用Java Agent來修飾線程池實現類』,執行工程下的腳本run-agent-demo.sh即可運行Demo。

Java Agent的使用方式在什么情況下TTL會失效

由於Runnable和Callable的修飾代碼,是在線程池類中插入的。下面的情況會讓插入的代碼被繞過,傳遞會失效。

  • 用戶代碼中繼承java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor, 覆蓋了execute、submit、schedule等提交任務的方法,並且沒有調用父類的方法。
    修改線程池類的實現,execute、submit、schedule等提交任務的方法禁止這些被覆蓋,可以規避這個問題。

  • 目前,沒有修飾java.util.Timer類,使用Timer時,TTL會有問題。

Java API Docs

當前版本的Java API文檔地址: http://alibaba.github.io/transmittable-thread-local/apidocs/

Maven依賴

示例:

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

可以在 search.maven.org 查看可用的版本。

FAQ

  • Mac OS X下,使用javaagent,可能會報JavaLaunchHelper的出錯信息。
    JDK Bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021205
    可以換一個版本的JDK。我的開發機上1.7.0_40有這個問題,1.6.0_51、1.7.0_45可以運行。
    # 1.7.0_45還是有JavaLaunchHelper的出錯信息,但不影響運行。

更多文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM