InheritableThreadLocal还存在的问题
[toc]
问题场景
我们使用线程时候往往不会只是简单的new Thread对象,而是使用线程池,当然使用线程池的好处很多,那么线程池会给InheritableThreadLocal带来什么问题,我们列举一下线程池的特点:
- 为了减少创建线程的开销,线程池会缓存已经使用过的线程
- 生命周期统一管理,合理的分配系统资源(线程生命周期:新建状态、就绪状态、运行状态、阻塞状态、死亡状态)
对于第一点,如果子线程已经被使用过了,并且set了新的值到ThreadLocal中,那么第二个任务进来还能获得父线程的值吗?
final InheritableThreadLocal<Span> inheritableThreadLocal=new InheritableThreadLocal<>();
inheritableThreadLocal,set(new Span("xiexiexie"));
//输出xiexiexie
inheritableThreadLocal.get();
ExecutorService es=Executors.newFixedThreadPool(1);
es.execute(()->{
System.out.println("========");
//输出 xiexiexie
inheritableThreadLocal.get();
inheritableThreadLocal.set(new Span("qiqiqi");
//输出 qiqiqi
inheritableThreadLocal.get();
}});
TimeUnit.SECONDS.sleep(1);
es.execute(()->{
System.out.println("========");
//输出qiqiqi
inheritableThreadLocal.get();
inheritableThreadLocal.set(new Span("qiqiqi");
//输出qiqiqi
inheritableThreadLocal.get();
}});
TimeUnit.SECONDS.sleep(1);
System.out.prinln("========");
//输出xiexiexie
Span span = inheritableThreadLocal.get();
static class Span{
public String name;
public int age;
public Span(String name){
this.name=name;
}
}
输出结果是:
xiexiexie
========
xiexiexie
qiqiqi
========
qiqiqi
qiqiqi
========
xiexiexie
造成这个结果的原因:只有创建子线程的时候才会设置子线程的inheritableTreadLocals
值,假如第一次提交的任务是A,第二次是B,B任务提交任务时使用的是A的任务的缓存线程,A任务执行时已经重新set了InheritableThreadLocals,值已经变为qiqiqi,B任务再次获取时候直接从t.inheritableThreadLocals中获取,所以获得的是A任务提交的值,而不是父线程的值(父线程值没有改变的原因是子线程set的值,只会set到子线程对应的t.inheritableThreadLocals中,不会影响父线程的inheritableThreadLocals)
解决思路
在submit新任务的时候在重新copy父线程的所有的Entry,然后重新给t.inheritableThreadLocals赋值,这样就解决线程池中每一个新的任务都能获得父线程中的ThreadLocal的值,而不受其他任务影响,因为在生命周期完成时候会自动clear所有数据。
解决方案
第三方库
Alibaba的一个库解决了这个问题,github:alibaba/transmittable-thread-local(详细见GitHub文档)
自定义RunTask类
自定一个RunTask类,使用反射加代理的方式来实现业务,主线程存在InheritableThreaadLocal中的值间接复制,详细如下:
- 定义一个InheritableTask抽象类,这个类实现了Runable接口,并定义一个runTask抽象方法,当开发者需要面对线程池,获取InheritableThreadLocal值的场景提交任务只需要集成InheritableTask类,实现runTask方法即可。
- 在创建任务时,也就是InheritableTask构造方法中,通过反射获取提交任务的业务线程的inheritableLocals属性,然后复制一份,暂存到当前的task的inheritableThreadLocalsObj属性找那个
- 线程池在执行该任务时,其实就是去掉用run()方法,在执行run方法时,先将inheritableThreadLocalsObj属性复制给当前执行任务的那个业务线程的inheritableThreadLocals属性值,然后再去执行runTask()方法,就是真正的业务逻辑,最后finally清理掉执行当前业务的线程的inheritableThreadLocals属性。
详细代码如下:
public abstract class InheritableTask implements Runnable {
private Object inheritableThreadLocalsObj;
public InheritableTask() {
try {
//获取当前业务线程
Thread currentThread = Thread.currentThread();
//获取inheritableThreadLocals属性值
Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocalsField.setAccessible(true);
//得到当前线程inheritableThreadLocals的属性值
Object threadLocalMapObj = inheritableThreadLocalsField.get(currentThread);
if (null != threadLocalMapObj) {
//获取字段的类型
Class<?> threadLocalMapClazz = inheritableThreadLocalsField.getType();
//获取ThreadLocal中的createInheritedMap方法
Method method = ThreadLocal.class.getDeclaredMethod("createInheritedMap", threadLocalMapClazz);
method.setAccessible(true);
//调用createInheritedMap方法,重新创建一个新的inheritableThreadLocals,并且将这个值保存
this.inheritableThreadLocalsObj = method.invoke(ThreadLocal.class, threadLocalMapObj);
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
@Override
public void run() {
//此处获取处理当前业务的线程,也就是线程池中的线程
Thread currentThread = Thread.currentThread();
Field field = null;
try {
//获取inheritableThreadLocals属性
field = Thread.class.getDeclaredField("inheritableThreadLocals");
//设置权限
field.setAccessible(true);
if (this.inheritableThreadLocalsObj != null) {
//将暂存值,赋值给currentThread
field.set(currentThread, this.inheritableThreadLocalsObj);
inheritableThreadLocalsObj = null;
}
//执行任务
runTask();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
try {
//最后将线程的InheritableThreadLocals设置为null
if (field != null) {
field.set(currentThread, null);
}
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
/**
* 代理方法这个方法处理业务逻辑
*/
public abstract void runTask();
下面是测试用例:
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("qis");
System.out.println(inheritableThreadLocal.get());
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
System.out.println(inheritableThreadLocal.get());
inheritableThreadLocal.set("qishuo");
System.out.println(inheritableThreadLocal.get());
});
Thread.sleep(1000);
System.out.println("----------------");
executorService.execute(() -> {
System.out.println(inheritableThreadLocal.get());
});
Thread.sleep(1000);
System.out.println("--------------分隔新以上是没有使用InheritableTask----------------");
executorService.submit(new InheritableTask() {
@Override
public void runTask() {
System.out.println(inheritableThreadLocal.get());
inheritableThreadLocal.set("qishuo");
System.out.println(inheritableThreadLocal.get());
}
});
Thread.sleep(1000);
System.out.println("----------------");
executorService.submit(new InheritableTask() {
@Override
public void runTask() {
System.out.println(inheritableThreadLocal.get());
}
});
}
}
输出结果:
qis
qis
qishuo
----------------
qishuo
--------------分隔新以上是没有使用InheritableTask----------------
qis
qishuo
----------------
qis
这样就解决了在线程池场景下的InheritableThreadLocal无效的问题,然而反射比较耗性能,一般优化反射的两种方式,一种使用缓存,一种使用性能较高的反射工具比如RefelectASM类。
下面展示使用缓存的实现:
public abstract class InheritableTaskWithCache implements Runnable {
private Object threadLocalsMapObj;
private static volatile Field inheritableThreadLocalsField;
private static volatile Class threadLocalMapClazz;
private static volatile Method createInheritedMapMethod;
private static final Object accessLock = new Object();
public InheritableTaskWithCache() {
try {
Thread currentThread = Thread.currentThread();
Field field = getInheritableThreadLocalsField();
//得到当前线程的inheritableThreadLocals的值ThreadLocalMap
Object threadLocalsMapObj = field.get(currentThread);
if (null != threadLocalsMapObj) {
Class threadLocalMapClazz = getThreadLocalMapClazz();
Method method = getCreateInheritedMapMethod(threadLocalMapClazz);
//创建一个新的ThreadLocalMap
this.threadLocalsMapObj = method.invoke(ThreadLocal.class, threadLocalsMapObj);
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private Field getInheritableThreadLocalsField() {
if (null == inheritableThreadLocalsField) {
synchronized (accessLock) {
if (null == inheritableThreadLocalsField) {
try {
Field field = Thread.class.getDeclaredField("inheritableThreadLocals");
field.setAccessible(true);
inheritableThreadLocalsField = field;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
}
return inheritableThreadLocalsField;
}
private Method getCreateInheritedMapMethod(Class threadLocalMapClazz) {
if (null != threadLocalMapClazz && null == createInheritedMapMethod) {
synchronized (accessLock) {
if (null == createInheritedMapMethod) {
try {
Method method = ThreadLocal.class.getDeclaredMethod("createInheritedMap", threadLocalMapClazz);
method.setAccessible(true);
createInheritedMapMethod = method;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
}
return createInheritedMapMethod;
}
private Class getThreadLocalMapClazz() {
if (null == inheritableThreadLocalsField) {
return null;
}
if (null == threadLocalMapClazz) {
synchronized (accessLock) {
if (null == threadLocalMapClazz) {
threadLocalMapClazz = inheritableThreadLocalsField.getType();
}
}
}
return threadLocalMapClazz;
}
/**
* 代理方法这个方法处理业务逻辑
*/
protected abstract void runTask();
@Override
public void run() {
Thread currentThread = Thread.currentThread();
Field field = getInheritableThreadLocalsField();
try {
if (null != threadLocalsMapObj && null != field) {
field.set(currentThread, threadLocalsMapObj);
threadLocalsMapObj = null;
}
runTask();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
try {
if (field != null) {
field.set(currentThread, null);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
}
综上,通过一个抽象的InheritableTask解决了线程池场景下InheritableThreadLocal失效问题。
总结
- InheritableThreadLocal在线程池中无效的原因是只有在创建线程Thread时才会去赋值父线程的InheritableThreadLocal中的值,而线程池场景下,主业务线程仅仅是提交任务的队列中的
- 如果要解决这个问题,可以自定义一个RunTask类,通过反射加代理的方式来实现业务主线程存在InheritableThreadLocal中值的间接复制,或者使用阿里开源的transmittable-thread-local。