ThreadLocal父子執行緒與執行緒池解決方案
ThreadLocal 想必大家都比較熟悉了,經常被大家稱作執行緒本地變數或者執行緒本地儲存,每個執行緒內部都會有一個該變數的副本,可以線上程內部任何地方使用。在專案開發過程中經常會有一些使用場景,比如將某個變數(訪問IP、requestId等)繫結到一個執行緒上,以便隨時使用,這些變數一般會有一個共同的特點:業務無關、運維需要。還有一些工具元件中也經常用到ThreadLocal,比如日誌框架中MDC、呼叫鏈ID的上下文傳遞、資料庫讀寫分離元件等。
ThreadLocal的問題描述
經常使用ThreadLocal的同學都知道,它是非常方便的,比如有個引數在很多處理邏輯中都會用到,但是又不想在每個函式的引數列表中寫上它,原因可能是該引數本身業務無關,在函式中來回傳遞,感覺怪怪的。
使用ThreadLocal的時候經常會遇到兩個問題:
- 建立多執行緒非同步執行業務邏輯時,該ThreadLocal變數並不能傳遞到子執行緒中;
- 當解決了第一個問題之後,在使用執行緒池的時候,Java中有的執行緒池存線上程複用的情況,這時候ThreadLocal變數也被複用了,顯然這是不符合預期;
解決問題
解決第一個問題,即父子執行緒中傳遞ThreadLocal變數。可以使用InheritableThreadLocal
替代ThreadLocal
定義變數,即可解決問題。這是Java自帶的一個類,無需引入任何第三方程式碼庫。
要解決第二個問題,即執行緒池中執行緒複用所導致的ThreadLocal變數錯亂。解決這個問題就有些麻煩了,還好有alibaba出品的開原始碼庫
transmittable-thread-local
提供了簡便的方法。首先定義變數要使用TransmittableThreadLocal
,然後定義的執行緒池需要特殊修飾,比如這樣execService = TtlExecutors.getTtlExecutorService(execService);
,如果不方便修改執行緒池定義的程式碼,或者不能保證修改完全,alibaba還提供了另外一種方式來修飾執行緒池,就是Java agent的方式,只需要在Java程序的啟動命令中新增-javaagent:/Users/xxx/.m2/repository/com/alibaba/transmittable-thread-local/2.10.2/transmittable-thread-local-2.10.2.jar
即可實現修飾所有執行緒池的目的。
實現原理
具體實現原理可以參考官方文件,和這個issue:https://github.com/alibaba/transmittable-thread-local/issues/123 ,這裡我就不介紹了。