父子线程和线程池如何实现threadLocal变量传递
Posted MyClass社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了父子线程和线程池如何实现threadLocal变量传递相关的知识,希望对你有一定的参考价值。
上一次我们看了ThreadLocal的原理和实现,今天我们看看下面几个问题:
1.多线程中父子线程,子线程如何获取父线程的变量?
2.主线程和线程池的线程本地副本变量如何实现复用隔离?
一、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
上面例子可以看出inheritableThreadLocal可以在子线程获取值,但是threadLocal不可以。InheritableThreadLocal其实继承ThreadLocal,其中重写了几个方法来操作ThreadLocalMap。主线程创建新线程的时候会获取当前线程的inheritableThreadLocals,并且将当前线程的该变量赋值给inheritableThreadLocals。这里在子线程修改引用变量的值,父线程也是可以获取的。
二、TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba开源的封装类。解决线程池线程或者缓存线程框架的ThreadLocal复用传递问题,需配合 TtlRunnable 和 TtlCallable 使用。下面看看TransmittableThreadLocal如何实现主线程和线程池中线程ThreadLocal复用和隔离的。
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());
}
});
}
}));
}
}
运行结果:
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 可以实现主线程和线程池之间的复用传递,不管是否是已缓存的线程,都可以实现线程池线程之间的复用隔离效果。
三、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来完成。
以上是关于父子线程和线程池如何实现threadLocal变量传递的主要内容,如果未能解决你的问题,请参考以下文章
ThreadLocal 搭配线程池使用造成内存泄漏的原因和解决方案
面试官:ThreadLocal 搭配线程池时为什么会造成内存泄漏?