上一次咱們看了ThreadLocal的原理和實現,今天咱們看看下面幾個問題:java
1.多線程中父子線程,子線程如何獲取父線程的變量?
2.主線程和線程池的線程本地副本變量如何實現複用隔離?
緩存
1、InheritableThreadLocal的使用
多線程中父子線程,子線程如何獲取父線程的變量?下面就是InheritableThreadLocal的示例:微信
public static void main(String[] args) {
InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
java.lang.ThreadLocal threadLocal = new ThreadLocal();
inheritableThreadLocal.set("inheritableThreadLocal-value");
threadLocal.set("threadLocal-value");
System.out.println("main thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
System.out.println("main thread --- threadLocal:"+threadLocal.get());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("new thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
System.out.println("new thread --- threadLocal:"+threadLocal.get());
}
});
thread.start();
}
多線程
運行結果:框架
main thread --- inheritableThreadLocal:inheritableThreadLocal-value
main thread --- threadLocal:threadLocal-value
new thread --- inheritableThreadLocal:inheritableThreadLocal-value
new thread --- threadLocal:null
ide
上面例子能夠看出inheritableThreadLocal能夠在子線程獲取值,可是threadLocal不能夠。InheritableThreadLocal其實繼承ThreadLocal,其中重寫了幾個方法來操做ThreadLocalMap。主線程建立新線程的時候會獲取當前線程的inheritableThreadLocals,而且將當前線程的該變量賦值給inheritableThreadLocals。這裏在子線程修改引用變量的值,父線程也是能夠獲取的。函數
2、TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba開源的封裝類。解決線程池線程或者緩存線程框架的ThreadLocal複用傳遞問題,需配合 TtlRunnable 和 TtlCallable 使用。下面看看TransmittableThreadLocal如何實現主線程和線程池中線程ThreadLocal複用和隔離的。this
public static void main(String[] args) {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
//ExecutorService裝換,將runnable轉爲TtlRunnable,爲了配合TransmittableThreadLocal使用,沒有其餘邏輯,能夠理解和普通線程池同樣
ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(3));
for(int i=0;i<5;i++) {
int j = i;
//賦值threadLocal
inheritableThreadLocal.set("inheritableThreadLocal-value-"+j);
transmittableThreadLocal.set("transmittableThreadLocal-value-"+j);
pool.execute(new Thread(new Runnable() {
@Override
public void run() {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : " + inheritableThreadLocal.get()+"-----"+transmittableThreadLocal.get());
}
});
}
}));
}
}
spa
運行結果:.net
pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-0
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-3
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-4
pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-1
pool-1-thread-3 : inheritableThreadLocal-value-2-----transmittableThreadLocal-value-2
結果分析:
inheritableThreadLocal在主線程和線程池之間能夠傳遞的,可是歸還線程池的線程inheritableThreadLocal值是一直不變的,若是是新開闢的線程,則會傳遞。因此inheritableThreadLocal仍是不能解決線程池和主線程之間的複用問題,由於緩存的線程會直接使用老的inheritableThreadLocal的值。
能夠看出transmittableThreadLocal 能夠實現主線程和線程池之間的複用傳遞,無論是不是已緩存的線程,均可以實現線程池線程之間的複用隔離效果。
3、TransmittableThreadLocal源碼
首先看一下TransmittableThreadLocal的主要設計邏輯是什麼:
1.首先須要使用輔助線程類TtlRunnable封裝了Runnable執行邏輯;
2.而後TtlRunnable中會TransmittableThreadLocal.copy()獲取當前父線程的Map<TransmittableThreadLocal<?>, Object>;
3.執行前會經過backupAndSetToCopied賦值給當前線程;
4.線程執行完run方法後,經過restoreBackup()再備份一遍。我想應該是防止你線程進行修改。
//構造TtlRunnable
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) {
return null;
}
if (runnable instanceof TtlRunnable) {
if (idempotent) {
//避免多餘的裝飾,並確保冪等性
return (TtlRunnable) runnable;
} else {
throw new IllegalStateException("Already TtlRunnable!");
}
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
//構造函數
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
//1.這裏將父線程的TransmittableThreadLocal變量值進行一次深拷貝
this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* TtlRunnable的run函數
*/
@Override
public void run() {
//2.獲取當前父線程的 TransmittableThreadLocal對象
Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//3.TransmittableThreadLocal對象賦值給線程池該執行線程的ThreadLocal,而且返回出來
Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
try {
//4.這裏在正常執行線程,此時的線程已經有了最新的TransmittableThreadLocal對象
//若是是純InheritableThreadLocal,是沒有任何效果的
runnable.run();
} finally {
//6.最終將一開始的Map<TransmittableThreadLocal<?>, Object>恢復,防止你線程進行修改
TransmittableThreadLocal.restoreBackup(backup);
}
}
總結
從上面幾個例子中能夠得出,父子線程之間想要傳遞線程本地變量須要依賴InheritableThreadLocal,此時ThreadLocal變量只能做用單獨的線程。可是線程池等線程緩存類框架中,要想實現主線程和線程池之間實現複用隔離效果,則可使用TransmittableThreadLocal來完成。
本文分享自微信公衆號 - MyClass社區(MyClass_ZZ)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。