一. 问题记录
线上使用ThreadLocal
临时存储用户信息传递到其它下游服务,但是偶尔会出现下游获取的用户信息不是当前用户的情况,经过详细排查,发现了是ThreadLocal
导致的问题。
二. 问题原因
ThreadLocal
在异步场景下是无法给子线程共享父线程中创建的线程副本数据的,也就是说当前线程的子线程无法获取到当前线程ThreadLocal
中的值。
三. 解决方案
1. InheritableThreadLocal(ITL)
InheritableThreadLocal
是JDK的一个类
public class InheritableThreadLocalDemo {
public static void main(String[] args) {
ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal.set("父类数据:threadLocal");
inheritableThreadLocal.set("父类数据:inheritableThreadLocal");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程获取父类ThreadLocal数据:" + ThreadLocal.get());
System.out.println("子线程获取父类inheritableThreadLocal数据:" + inheritableThreadLocal.get());
}
}).start();
}
}
打印结果:
子线程获取父类ThreadLocal数据:null
子线程获取父类inheritableThreadLocal数据:父类数据:inheritableThreadLocal
实现原理是子线程是通过在父线程中通过调用new Thread()
方法来创建子线程,Thread#init
方法在Thread
的构造方法中被调用。在init
方法中拷贝父线程数据到子线程中:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
但InheritableThreadLocal
仍然有缺陷,一般我们都是使用的线程池,而InheritableThreadLocal
是在new Thread
中的init()
方法给赋值的,而线程池是线程复用的逻辑,所以这里还是会存在问题。
2. TransmittableThreadLocal(TTL)
TransmittableThreadLocal(TTL)
是阿里巴巴开源的组件,可以解决线程池下ThreadLocal
值传递问题。
四. 总结
单线程:ThreadLocal
Thread创建的线程:InheritableThreadLocal
线程池创建的线程:TransmittableThreadLocal
(推荐)