上一次咱们看了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源创计划”,欢迎正在阅读的你也加入,一块儿分享。