threadLocal
的特点就是与线程绑定,一般通过这种隐式传参的方式来传递上下文。
比如,用户登录和获取用户相关信息,这时候如果在每个需要用户信息的方法入参上加入用户信息参数就先的非常冗余,不够优雅。
还有就是日志的链路信息等等。
使用方式比较简单,一般是在入口处设置,然后在后续方法中获取使用
private static ThreadLocal<String> USER_CONTEXT = new ThreadLocal<>();
...
try {
USER_CONTEXT.set("username");
String userInfo = USER_CONTEXT.get();
System.out.println(userInfo);
} finally {
USER_CONTEXT.remove();
}
要搞清楚其中原理,通过set和get方法的源码就行了。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
Map
(ThreadLocalMap
)源码实现非常简单,但有一个问题需要解决。
ThreadLocalMap存放在哪?ThreadLocal中吗?
答:显然不是,存放在Thread中。原因是在于如果将ThreadLocalMap维护在ThreadLocal中,那么显然在Thread执行完毕后,需要去清除掉ThreadLocal中存储的信息,如果忘记或者因为特殊原因线程中断掉了,那么将会导致ThreadLocal中数据不会被释放。如果是存放于Thread中,那么维护问题就解决了,线程不管是正常还是异常关闭,其中使用得内存都会得到释放。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread中的成员变量
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
get
方法也是一样,把ThreadLocal
对象当做Key
去Thread
的ThreadLocalMap
中获取Value
。
但是这样就意味着,我们无法使用多线程来处理任务,因为set
的时候只在当前Thread
的ThreadLocalMap
中存放了我们的数值,如果在创建一个新的Thread
的话,那么显然其中的Map
不会有我们设置的值。
USER_CONTEXT.set("username");
Thread newThread = new Thread(() -> System.out.println(USER_CONTEXT.get()));
其中newThread
打印出来的就是null
这时候聪明的小伙伴就发现,那我在创建新线程的时候将上一个线程的ThreadLocalMap
带过来不就行了嘛,这就是我们要讨论的第二个类InheritableThreadLocal
根据上面的问题我们知道ThreadLocal
存在的局限性,InheritableThreadLocal
也就是可继承的ThreadLocal,这里的可继承就是指的ThreadLocalMap
。
它实现可继承的方式,就如前文描述的一样,在创建新线程的时候,将当前线程的ThreadLocalMap
设置到新线程中,但不再是threadLocals
成员属性,而是inheritableThreadLocals
成员属性。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
//获取Map变成了inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//创建Map变成了inheritableThreadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
其中需要关注的一点是,从当前线程线程拷贝到新线程只发生在创建的时候,也就是new Thread()
的时刻。
这也就导致了,在日常开发中使用得场景非常少,因为我们大多使用的是线程池,部分线程是不会被销毁的,那么就只会在第一次创建的时候拷贝,这就失去了意义,线程池感兴趣的同学,可以看看我的另一篇文章【ThreadPoolExecutor源码详解】
所以,在日常开发中,其实该类是非常鸡肋的,覆盖的场景比ThreadLocal
多不了多少。
那么对于我们日常开发中使用线程池的这种方式,有现成的解决方案吗?
有的,阿里开源TransmittableThreadLocal【github地址:https://github.com/alibaba/transmittable-thread-local】
InheritableThreadLocal
的思路是在创建的时候进行拷贝。
而TransmittableThreadLocal
的思路则是在运行前(run()
之前拷贝) 【TransmittableThreadLocal详解】。