- 快速入手
- 源碼設計解讀
2.1 TransmittableThreadLocal的原理
2.2 holder變量的設計
2.3 TTL為什么不繼承ThreadLocal
2.4 為什么會有restore步驟 - 使用事項
3.1 是否存在線程安全問題
-3.1.1 子線程修改TTL,父線程能否感應到TTL變化此衅?
-3.1.2 父線程修改TTL遍烦,子線程能否感應到TTL變化罕拂?
-3.1.3 線程不安全的解決方案
3.2 是否存在內(nèi)存泄漏問題
3.3 子線程中需要調(diào)用remove方法嗎?
3.4 主線程執(zhí)行remove方法會清空子線程的TTL的值嗎妒潭?
3.5 如何創(chuàng)建出默認值的TTL
3.6 SpringBoot啟動時焙蹭,調(diào)用TTL的get方法如何保證線程安全
3.7 創(chuàng)建出安全的TTL的方式 - 相關文章
1. 快速入手
github地址:https://github.com/alibaba/transmittable-thread-local
pom依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.5</version>
<scope>compile</scope>
</dependency>
業(yè)務使用:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.tellme.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@RestController
public class ThreadLocalController {
ExecutorService executorService =
TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
public static TransmittableThreadLocal<String> l2 = new TransmittableThreadLocal<>();
public static TransmittableThreadLocal<User> local = new TransmittableThreadLocal<>();
@RequestMapping("/local/t1")
public void t1() {
//第一次使用線程池時前,創(chuàng)建多個ThreadLocal的值论悴。
local.set(new User("001", "sam"));
l2.set("李白不偷藍");
executorService.execute(() -> {
log.info("【/local/t1的add操作】子線程打印數(shù)據(jù){}", local.get());
});
executorService.execute(() -> {
log.info("【/local/t1的add操作】子線程打印數(shù)據(jù){}", local.get());
});
local.remove();
l2.remove();
}
@RequestMapping("/local/t4")
public void t4() {
executorService.execute(() -> {
log.info("【/local/t4】子線程打印數(shù)據(jù){}", l2.get());
});
}
}
2. 源碼設計解讀
2.1 TransmittableThreadLocal的原理
在設計模式上采用裝飾器模式去增強Runnable等任務。在向線程池提交Runnable任務時墓律,去裝飾Runnable膀估,將主線程的ThreadLocal通過構造方法傳遞到Runnable對象。然后在run()方法中設置進去耻讽。
注意點:TransmittableThreadLocal需要配套使用TtlExecutors
察纯。
TtlRunnable源碼分析
@Override
public void run() {
/**
* capturedRef是主線程傳遞下來的ThreadLocal的值。
*/
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
/**
* 1. backup(備份)是子線程已經(jīng)存在的ThreadLocal變量针肥;
* 2. 將captured的ThreadLocal值在子線程中set進去饼记;
*/
Object backup = replay(captured);
try {
/**
* 待執(zhí)行的線程方法;
*/
runnable.run();
} finally {
/**
* 在子線程任務中慰枕,ThreadLocal可能發(fā)生變化具则,該步驟的目的是
* 回滾{@code runnable.run()}進入前的ThreadLocal的線程
*/
restore(backup);
}
}
即步驟總結為:
- 裝飾Runnable,將主線程的TTL傳入到TtlRunnable的構造方法中具帮;
- 將子線程的TTL的值進行備份乡洼,將主線程的TTL設置到子線程中(value是對象引用,可能存在線程安全問題)匕坯;
- 執(zhí)行子線程邏輯;
- 刪除子線程新增的TTL拔稳,將備份還原重新設置到子線程的TTL中葛峻;
詳細解讀見下文:
- 2.4 為什么會有restore步驟
- 3.1 是否存在線程安全問題
- 3.3 子線程中需要調(diào)用remove方法嗎?
2.2 holder變量的設計
疑問:主線程需要將TransmittableThreadLocal傳遞到子線程巴比,那么主線程如何記錄所有的TransmittableThreadLocal术奖?
Thread中存在下面兩個變量礁遵,存儲的是ThreadLocal的值和InheritableThreadLocal的值。并且ThreadLocal和InheritableThreadLocal分別實現(xiàn)了get()方法采记,可以獲取到對應的ThreadLocalMap佣耐。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- TransmittableThreadLocal繼承ThreadLocal,那么便去threadLocals填充值唧龄;
- TransmittableThreadLocal繼承InheritableThreadLocal兼砖,那么便去inheritableThreadLocals填充值;
TransmittableThreadLocal無論繼承哪個類既棺,若只是將TransmittableThreadLocal傳遞到子線程(粒度蟹硇),那么就不能簡單的使用get()方法丸冕。
于是:TransmittableThreadLocal需要重新創(chuàng)建一個線程級別的緩存
耽梅,來記錄某個線程所有的TransmittableThreadLocal對象。
線程級別的緩存——在TransmittableThreadLocal創(chuàng)建一個ThreadLocal胖烛。
源碼位置:TransmittableThreadLocal
// Note about the holder:
// 1. holder self is a InheritableThreadLocal(a *ThreadLocal*).
// 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal<Object>, ?>.
// 2.1 but the WeakHashMap is used as a *Set*:
// - the value of WeakHashMap is *always null,
// - and be never used.
// 2.2 WeakHashMap support *null* value.
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}
};
這就是holder的由來:無法對Thread進行擴展眼姐,且只想將TransmittableThreadLocal傳遞到子線程。即自己去維護一個線程級別的全局緩存佩番。
-
static
修飾:一個線程中众旗,無論TransmittableThreadLocal被創(chuàng)建多少次,需要保證維護的是同一個緩存答捕。即static保證逝钥; -
InheritableThreadLocal
:這個后續(xù)展開,其實和單純使用ThreadLocal區(qū)別不大拱镐; -
WeakHashMap
:弱引用(發(fā)生GC便回收)艘款,這個是為了確保本框架不會造成潛在的內(nèi)存泄漏;
2.3 TTL為什么不繼承ThreadLocal
disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
InheritableThreadLocal是一個潛在的泄漏問題沃琅。新創(chuàng)建的線程永遠不知道如何處理繼承的值哗咆。應在任務的生命周期內(nèi)創(chuàng)建和清除可傳輸?shù)木€程本地值。InheritableThreadLocal 會將可傳輸線程本地值的管理泄漏到任務之外益眉。
測試代碼見:TransmittableThreadLocal會不會有內(nèi)存泄漏的風險晌柬? #281
作者設計理念:
當然:TransmittableThreadLocal也是可以關閉Inheritable的。詳見
- 對于線程池 TTL提供的DisableInheritable 支持
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/threadpool/DisableInheritableThreadFactory.html - disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
2.4 為什么會有restore步驟
- Runnable可能由主線程執(zhí)行郭脂,若Runnable修改了TransmittableThreadLocal年碘,可能會造成主線程的TransmittableThreadLocal值變化,造成bug展鸡;
- restore中由框架進行了ttl的remove操作屿衅,無需開發(fā)人員在子線程中顯式調(diào)用remove()方法,也不會造成內(nèi)存泄漏莹弊。
代碼位置:com.alibaba.ttl.TransmittableThreadLocal.Transmitter#restoreTtlValues
3. 使用事項
3.1 是否存在線程安全問題
異步線程中修改ThreadLocal值涤久,主線程也會被修改涡尘?(DB Session傳遞場景) #253
TransmittableThreadLocal
有restore
的操作,但是子線程中修改了對象的引用响迂,主線程是可以感知到的考抄。可能存在線程安全問題蔗彤。
3.1.1 子線程修改TTL川梅,父線程能否感應到TTL變化?
默認是可以的幕与,線程不安全問題的具體體現(xiàn)挑势,解決方案詳見
3.1.3 線程不安全的解決方案
。
3.1.2 父線程修改TTL啦鸣,子線程能否感應到TTL變化潮饱?
默認是可以的,線程不安全問題的具體體現(xiàn)诫给,解決方案詳見
3.1.3 線程不安全的解決方案
香拉。
3.1.3 線程不安全的解決方案
在restore方法中(詳見
2.4 為什么會有restore步驟
),會remove掉子線程的TTL中狂。但是在replay
方法父子線程傳遞的是對象引用凫碌。父子線程修改對象便可能會導致線程不安全問題。
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
/**
* 完成對象的copy胃榕,對于復雜的對象盛险,可以采用序列化反序列化的方式進行深拷貝
*/
@Override
public Map<String, String> copy(Map<String, String> parentValue) {
return new HashMap<>(parentValue);
}
};
注意:深拷貝的問題。JAVA基礎篇(3)-深克隆與淺克隆
3.2 是否存在內(nèi)存泄漏問題
TransmittableThreadLocal會不會有內(nèi)存泄漏的風險勋又? #281
Inheritable
能力/功能 (即第一次創(chuàng)建Thread
時的上下文傳遞)引發(fā)的問題苦掘。
解決方法 參見 #279 (comment) : @yexuerui
TTL提供了 關閉
Inheritable
能力的解法:
(可能要理解一下 這個解法及其背景原因,有些復雜性楔壤。 <g-emoji class="g-emoji" alias="smile" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f604.png" style="box-sizing: border-box; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.25em; font-weight: 400; line-height: 1; vertical-align: -0.075em; font-style: normal !important;">??</g-emoji> )
- 參見
TransmittableThreadLocal
主類的javadoc
說明:
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/TransmittableThreadLocal.html
- 對于線程池 TTL提供的DisableInheritable 支持
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/threadpool/DisableInheritableThreadFactory.html- disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
這個Issue中解釋了 為什么要做關閉Inheritable
能力的原因
3.3 子線程中需要調(diào)用remove方法嗎鹤啡?
不需要,結果詳見
2.4 為什么會有restore步驟
3.4 主線程執(zhí)行remove方法會清空子線程的TTL的值嗎蹲嚣?
不會递瑰。
主線程調(diào)用remove方法,將TTL對應的value的引用指向null隙畜。
但是TTL傳遞到子線程抖部,子線程也會持有一個value的引用。依舊可以獲取到TTL的值议惰。
3.5 如何創(chuàng)建出默認值的TTL
- 創(chuàng)建出的TTL的value默認值不為null慎颗,可以重寫
initialValue
方法; - 因為TTL繼承與ITL,也可以重寫
childValue
方法哗总,實現(xiàn)new Thread()時父線程不向子線程傳遞變量;當然官方推薦使用DisableInheritableThreadFactory去裝飾我們線程池的ThreadFactory倍试;
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
//防止ITL的潛在的內(nèi)存泄漏(官方也提供對應的API去實現(xiàn))可以不重寫方法讯屈。
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
};
3.6 SpringBoot啟動時,調(diào)用TTL的get方法如何保證線程安全
https://github.com/alibaba/transmittable-thread-local/issues/282
因為TTL底層使用ITL县习,會導致在new線程的時候涮母,父子線程的數(shù)據(jù)傳遞,且無法銷毀躁愿。
背景:
- 項目啟動的時候叛本,存在TTL的get操作,于是main線程存在TTL的value彤钟;
- 當請求進入時来候,Tomcat線程池(不會被TtlExecutors裝飾)會開啟子線程來執(zhí)行業(yè)務邏輯;
- main線程會將TTL(此時僅可看做ITL)的值傳遞到子線程逸雹;
- 子線程修改TTL的引用時营搅,會造成內(nèi)存不安全;
代碼如下:
@Slf4j
@RestController
public class ThreadLocalController {
ExecutorService executorService =
TtlExecutors.getTtlExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()));
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
};
/**
* 項目啟動的時候梆砸,會調(diào)用TTL的get方法转质,這里使用static模擬;
*/
static {
l2.get();
log.info("項目啟動時加載配置");
}
@RequestMapping("/local/t1")
public void t1() throws InterruptedException {
Map<String, String> mc = l2.get();
mc.put("t1", "t1v");
log.info("【/local/t1】主線程打犹馈:" + l2.get());
executorService.execute(() -> {
log.info("【/local/t1】子線程2map{}", l2.get());
});
Thread.sleep(1000);
l2.remove();
}
@RequestMapping("/local/t4")
public void t4() {
log.info("【/local/t4】主線程打有菪贰:" + l2.get());
executorService.execute(() -> {
log.info("【/local/t4】子線程打印數(shù)據(jù){}", l2.get());
});
Map<String, String> cache = l2.get();
cache.put("l4", "l4v");
l2.remove();
}
}
疑問:此時由于是普通的線程池,即使TTL重寫copy方法也會造成線程不安全日矫;
解決方法只有去重寫childValue方法赂弓,來解決ITL傳遞到子線程嗎?:
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
};
3.7 創(chuàng)建出安全的TTL的方式
/**
* 環(huán)境變量數(shù)據(jù)
*/
private static final TransmittableThreadLocal<Map<String, String>> sealThreadLocalEnv = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new LinkedHashMap<>();
}
//解決普通線程池中使用TTL搬男,造成數(shù)據(jù)污染的問題
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
//父子線程使用的是拷貝對象拣展。而非簡單對象的引用。
@Override
public Map<String, String> copy(Map<String, String> parentValue) {
return new LinkedHashMap<>(parentValue);
}
};