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

功能

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

JDK的InheritableThreadLocal類能夠完成父子線程值的傳遞。 但對於使用線程池等會緩存線程的組件的狀況,線程由線程池建立好,而且線程是緩存起來反覆使用的;這時父子線程關係的上下文傳遞已經沒有意義,應用中要作上下文傳遞,其實是在把 任務提交給線程池時的上下文傳遞到 任務執行時。javascript

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

歡迎java

需求場景

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

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

  1. 分佈式跟蹤系統緩存

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

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

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

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_5一、1.7.0_45能夠運行。
    # 1.7.0_45仍是有JavaLaunchHelper的出錯信息,但不影響運行。

更多文檔

相關文章
相關標籤/搜索