有關ThreadLocal的介紹我之前一篇文章已經做了介紹:https://blog.csdn.net/qq_26012495/article/details/86475725
本篇主要解決,在父線程中開啓子線程時ThreadLocal存在的值傳遞問題,以及如何解決。
目錄
3. 修改爲InheritableThreadLocal代碼測試
4. InheritableThreadLocal在其他場景存在的問題
三、TransmittableThreadLocal(阿里開源)
1. 查看TransmittableThreadLocal源碼
一、ThreadLocal
1. 普通ThreadLocal存在的問題
先使用普通的ThreadLocal類做一個測試,在main方法中通過new Thread方法來開啓子線程,在子線程中打印父線程中set的ThreadLocal值。
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("初始化的值能繼承嗎?");
System.out.println("父線程的ThreadLocal值:" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程到了");
System.out.println("子線程的ThreadLocal值:" + threadLocal.get());
}
}).start();
}
子線程打印的結果爲null,說明父線程的ThreadLocal值在子線程中並未得到傳遞,而是中斷了。
二、InheritableThreadLocal
1. InheritableThreadLocal源碼分析
由於ThreadLocal父線程無法傳遞本地變量到子線程中,於是JDK引入了InheritableThreadLocal類,該類的首部描述:該類繼承了ThreadLocal類,用以實現父線程到子線程的值繼承;創建子線程時,子線程將接收父線程具有值的所有可繼承線程局部變量的初始值。通常子線程的值與父線程相同;子線程的值可以被父線程重寫的方法改變;
/**
* This class extends <tt>ThreadLocal</tt> to provide inheritance of values
* from parent thread to child thread: when a child thread is created, the
* child receives initial values for all inheritable thread-local variables
* for which the parent has values. Normally the child's values will be
* identical to the parent's; however, the child's value can be made an
* arbitrary function of the parent's by overriding the <tt>childValue</tt>
* method in this class.
*
* <p>Inheritable thread-local variables are used in preference to
* ordinary thread-local variables when the per-thread-attribute being
* maintained in the variable (e.g., User ID, Transaction ID) must be
* automatically transmitted to any child threads that are created.
*
* @author Josh Bloch and Doug Lea
* @see ThreadLocal
* @since 1.2
*/
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
共有如下三個方法,沒有什麼特殊的地方:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
我們以最簡單的創建線程爲例
Thread thread = new Thread(...)
2. Thread類源碼分析
通過查看Thread類源碼,其有兩個全局變量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread構造器,會調用init方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
再看init方法,對inheritableThreadLocal變量進行了初始化,將當前線程的inheritableThreadLocal賦值給子線程的inheritableThreadLocal,完成了父線程到子線程的變量傳遞。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
3. 修改爲InheritableThreadLocal代碼測試
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("初始化的值能繼承嗎?");
System.out.println("父線程的ThreadLocal值:" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程到了");
System.out.println("子線程的ThreadLocal值:" + threadLocal.get());
}
}).start();
}
運行結果,可看到在new Thread創建子線程的場景中,子線程已經成功打印了父線程的ThreadLocal值。
4. InheritableThreadLocal在其他場景存在的問題
但是InheritableThreadLocal仍然存在一個問題,上述我們解決了在創建Thread時的傳遞,其傳遞時機是在創建線程時;但在線程池複用的情景中,沒有創建Thread的觸發條件。
舉一個失敗案例:創建一個固定大小爲2的線程池,進行五次子線程執行,每次給父線程的threadLocal重新賦值,由於線程池大小爲2,因此只有前兩次觸發初始化線程池,後面三次均爲線程複用,理論上來說,子線程可繼承的父線程變量到i=1截止。
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
ExecutorService threadPool = Executors.newFixedThreadPool(2);// 創建一個固定大小爲2的線程池
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能繼承嗎?" + i);
System.out.println("父線程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("子線程到了");
System.out.println("=========子線程的ThreadLocal值:" + threadLocal.get());
}
});
}
}
運行結果如我所料,子線程打印的值只有0和1
三、TransmittableThreadLocal(阿里開源)
使用線程池時,ThreadLocal父線程到子線程值傳遞問題需要TransmittableThreadLocal來解決。TransmittableThreadLocal是阿里開源的專門用以解決上述問題的類,使用時需要引入maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.4</version>
</dependency>
TTL:https://github.com/alibaba/transmittable-thread-local#-%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF
1. 查看TransmittableThreadLocal源碼
其繼承了InheritableThreadLocal類,因此同樣也支持new Thread時的值傳遞;
其中set和get方法均調用了addValue方法,addValue方法使用了全局的holder變量,將this指針put入holder中,this指的就是當前線程的ThreadLocal【因爲ThreadLocal維護了線程和value的關係,不論是父線程還是子線程,都會被存入ThreadLocalMap中,因此此處的this包含了所有的父線程+子線程】。
holder就是一個InheritableThreadLocal變量,該變量在子線程的執行過程中,緩存了所有線程的TransmittableThreadLocal(包括子線程+父線程),set方法緩存父線程,get方法緩存子線程(多數情況都是父線程賦值,子線程調用,所以不論是get還是set方法都要給holder存值)。
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> {
static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap();
}
protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap(parentValue);
}
};
public final void set(T value) {
super.set(value);
if (null == value) {// 提供set null移除value的功能
this.removeValue();
} else {
this.addValue();
}
}
public final T get() {
T value = super.get();
if (null != value) {
this.addValue();
}
return value;
}
void addValue() {
if (!((Map)holder.get()).containsKey(this)) {
((Map)holder.get()).put(this, (Object)null);
}
}
}
2. TtlExecutors類使用及源碼分析
上述問題在於線程池中線程複用沒有觸發條件,而只要在子線程執行時加入複製父線程的threadLocal即可。而原生的線程池創建方法不提供此功能,因此需要單獨的線程池創建類支持,TtlExecutors,同樣也是阿里開源的。
使用場景如下:
ExecutorService threadPool= TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(poolSize));
修改剛纔的代碼,ThreadLocal和Executors修改爲阿里的 TransmittableThreadLocal和TtlExecutors:
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能繼承嗎?" + i);
System.out.println("父線程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("子線程到了");
System.out.println("=========子線程的ThreadLocal值:" + threadLocal.get());
}
});
}
}
再來看TtlExecutors類,其getTtlExecutorService方法如下,返回的是ExecutorServiceTtlWrapper類
public static ExecutorService getTtlExecutorService(ExecutorService executorService) {
return (ExecutorService)(executorService != null && !(executorService instanceof ExecutorServiceTtlWrapper) ? new ExecutorServiceTtlWrapper(executorService) : executorService);
}
再去看ExecutorServiceTtlWrapper,該類中未尋找到execute方法,因此在其父類ExecutorTtlWrapper中
class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService {
execute方法中,執行Runnable時,外層包裝了TtlRunnable類
class ExecutorTtlWrapper implements Executor {
final Executor executor;
public ExecutorTtlWrapper(Executor executor) {
this.executor = executor;
}
public void execute(Runnable command) {
this.executor.execute(TtlRunnable.get(command));
}
}
看下TtlRunnable類的run方法(多線程的執行處,到達run方法時,子線程內部邏輯已經執行完畢,包括子線程的ThreadLocal的get或者set),發現以下4句是關鍵語句,正常run方法執行邏輯應該只有3,TtlRunnable的run實際上是在正常run的前後包裝了邏輯:
- 一進入TtlRunnable就將當前的所有TransmittableThreadLocal存儲在AtomicReference(copied)中
- 在子線程執行前,將copied(即1獲取的)備份在backup中,copied中不存在的,holder中存在的,是剛剛加入的子線程threadLocal,把copied的全部remove掉,只留下該子線程的。
- 執行子線程(runnable的類是Runnable)
- 在子線程執行後,把剛剛執行的threadLocal清理掉,再把backup中的set回來,完成一次神操作。
public final class TtlRunnable implements Runnable {
private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef = new AtomicReference(TransmittableThreadLocal.copy());// 1
public void run() {
Map<TransmittableThreadLocal<?>, Object> copied = (Map)this.copiedRef.get();
if (copied != null && (!this.releaseTtlValueReferenceAfterRun || this.copiedRef.compareAndSet(copied, (Object)null))) {
Map backup = TransmittableThreadLocal.backupAndSetToCopied(copied);// 2
try {
this.runnable.run();// 3
} finally {
TransmittableThreadLocal.restoreBackup(backup);// 4
}
} else {
throw new IllegalStateException("TTL value reference is released after run!");
}
}
以下是2和4的源碼部分
static Map<TransmittableThreadLocal<?>, Object> backupAndSetToCopied(Map<TransmittableThreadLocal<?>, Object> copied) {
Map<TransmittableThreadLocal<?>, Object> backup = new HashMap();
Iterator iterator = ((Map)holder.get()).entrySet().iterator();
Entry entry;
TransmittableThreadLocal threadLocal;
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
backup.put(threadLocal, threadLocal.get());// 執行備份
if (!copied.containsKey(threadLocal)) {// 從holder中remove掉copied中不存在的
iterator.remove();
threadLocal.superRemove();
}
}
iterator = copied.entrySet().iterator();
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
threadLocal.set(entry.getValue());
}
doExecuteCallback(true);
return backup;
}
static void restoreBackup(Map<TransmittableThreadLocal<?>, Object> backup) {
doExecuteCallback(false);
Iterator iterator = ((Map)holder.get()).entrySet().iterator();
Entry entry;
TransmittableThreadLocal threadLocal;
// 把剛剛執行的那個清理掉,執行完不需要了,然後再把backup裏面的拿回來,恢復初始狀態
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
if (!backup.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
iterator = backup.entrySet().iterator();
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
threadLocal.set(entry.getValue());
}
}
四、總結
ThreadLocal
解決問題:
維護了線程和value的關係
存在問題:
無法解決父線程向子線程傳遞ThreadLocal值的問題
InheritableThreadLocal
解決問題:
JDK提供的,在通過new Thread創建子線程時,複製父線程的ThreadLocal值至子線程,觸發開關爲創建全新的子線程。
存在問題:
但線程池的線程複用機制打破了創建全新子線程的時機,導致子線程繼承到錯誤的父線程ThreadLocal(還是複用前的子線程那個值)
TransmittableThreadLocal
解決問題:
阿里開源的TTL包,配合TtlExecutors、TtlRunnable、TtlCallable實現在子線程執行時的ThreadLocal值傳遞。
解決思路:
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能繼承嗎?" + i);
System.out.println("父線程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {// 初始化TtlRunnable
@Override
public void run() {
System.out.println("子線程到了");
System.out.println("=========子線程的ThreadLocal值:" + threadLocal.get());
}
});
}
對照着剛纔的示例代碼
- 在每一次調用threadLocal.set方法,都將當前的ThreadLocal存入holder的key中,值爲null;
- threadPool.execute方法將Runnable包裝成TtlRunnable,調用該方法時,還沒進入子線程,完成TtlRunnable的初始化,TtlRunnable的初始化會將當前所有已經產生的ThreadLocal緩存;
- 未完待續