ThreadLocal与InheritableThreadLocal的实现原理
ThreadLocal介绍
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方法的源码就行了。
set()
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)
- 将值设置到Map
源码实现非常简单,但有一个问题需要解决。
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
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);
}
}
方案
那么对于我们日常开发中使用线程池的这种方式,有现成的解决方案吗? 有的,阿里开源TransmittableThreadLocal【github地址:】
InheritableThreadLocal的思路是在创建的时候进行拷贝。 而TransmittableThreadLocal的思路则是在运行前(run()之前拷贝) 。
