線程池如何傳遞線程上下文信息

 戳藍字「TopCoder」關注咱們哦!html

業務開發中,通常都會使用ThreadLocal保存一些上下文信息,可是在線程池中執行對應邏輯時,因爲是不一樣線程因此沒法獲取以前線程的上下文信息。git

線程池的線程上下文傳遞,實現方案就是在提交任務時記錄當前線程上下文信息,在線程池中線程執行用戶任務前將以前保存的上下文塞到當前線程的上下文中,在執行用戶任務以後移除該上下文便可。簡單來講就是,外部線程提交任務時要記錄上下文信息,內部線程執行任務時獲取以前記錄的上下文信息設置到當前線程上下文中。github

實現線程上下文傳遞的2種方式:web

  • 一種是在用戶任務中直接進行手動獲取/設置上下文邏輯。安全

  • 另外一種是實現一個自定義的線程池,在提交任務時對任務進行包裝並保存上下文信息,而後任務執行前設置上下文信息。微信

兩種實現方式的代碼以下:併發

private static ThreadLocal<String> CONTEXT = new ThreadLocal<>();
private static ExecutorService executor = new ThreadPoolExecutor(11,
        60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512));

private static ExecutorService executorWrap = new ThreadPoolExecutorWrap(11,
        60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512));

public static void main(String[] args) {
    CONTEXT.set("main context");

    // 方式1:在用戶任務中直接進行手動獲取/設置上下文邏輯
    executor.submit(new RunnableWrap(() -> System.out.println("hello world: " + CONTEXT.get())));

    // 方式2:自定義線程池,封裝成支持保存/設置上下文的任務
    executorWrap.submit(() -> System.out.println("hello world: " + CONTEXT.get()));
}

static class ThreadPoolExecutorWrap extends ThreadPoolExecutor {
    public ThreadPoolExecutorWrap(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public Future<?> submit(Runnable task) {
        if (task == null) {
            throw new NullPointerException();
        }
        RunnableFuture<Void> ftask = newTaskFor(new RunnableWrap(task), null);
        execute(ftask);
        return ftask;
    }
}

static class RunnableWrap implements Runnable {
    private String contextValue;
    private Runnable task;

    public RunnableWrap(Runnable task) {
        this.contextValue = CONTEXT.get();
        this.task = task;
    }

    @Override
    public void run() {
        try {
            CONTEXT.set(contextValue);
            // 用戶任務邏輯
            task.run();
        } finally {
            CONTEXT.remove();
        }
    }
}

關於線程間上下文傳遞,阿里給出了一個解決方案:TTL(transmittable-thread-local)是一個線程間傳遞ThreadLocal,異步執行時上下文傳遞的解決方案。整個庫的核心是構建在TransmittableThreadLocal類(繼承並增強InheritableThreadLocal類)之上,同時包含線程池修飾(ExecutorService/ForkJoinPool/TimerTask)以及Java Agent支持,代碼小於1k行,短小精悍。app

咱們都知道,JDK的InheritableThreadLocal類能夠完成父線程到子線程的值傳遞。但對於使用線程池等會池化複用線程的組件的狀況,線程由線程池建立好,而且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal值傳遞已經沒有意義,應用須要的其實是把 任務提交給線程池時的ThreadLocal值傳遞任務執行時。原理是使用TtlRunnable/Ttlcallable包裝了Runnable/Callable類:異步

  1. 在TtlRunnable/Ttlcallable初始化時capture TransmittableThreadLocal變量ide

  2. 在run方法調用runnable.run()前進行replay,設置到當前線程ThreadLocal

  3. 在run方法調用runnable.run()後進行restore,上下文還原,也就是replay的反向操做

注意,步驟1和步驟2/3不是在同一個線程中執行的,這個流程和本文最初說的實現方案是一致的。

TTL的示例代碼以下:

void testTtlInheritableThreadLocal() throws InterruptedException {
    ExecutorService executor = Executors.newFixedThreadPool(1);
    executor.submit(() -> {}); // 先進行工做線程建立

    // 使用TTL
    final TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<>();
    parent.set("value-set-in-parent");
    // 將Runnable經過TtlRunnable包裝下
    executor.submit(TtlRunnable.get(() -> System.out.println(Thread.currentThread().getName() + ": " + parent.get())));
}
// 輸出結果:pool-1-thread-1: value-set-in-parent

TTL實現原理和文章開頭說的實現線程上下文傳遞大體一致,感興趣的小夥伴能夠直接看下TTL源碼(https://github.com/alibaba/transmittable-thread-local),這裏再也不贅述。

最後關於ThreadLocal再提一下,咱們能夠重寫其initialValue方法,這樣能夠在threadLocal.get爲空時初始化一個值,使用示例以下:

ThreadLocal<String> local = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "init";
    }
};

System.out.println(local.get()); // init

local.set("hello world");
System.out.println(local.get()); // hello world

local.remove();
System.out.println(local.get()); // init


 推薦閱讀 


歡迎小夥伴 關注【TopCoder】 閱讀更多精彩好文。


本文分享自微信公衆號 - TopCoder(gh_12e4a74a5c9c)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索