相关文章:
ThreadLocal 存在的问题
在上一篇文章中简单介绍了什么是 ThreadLocal
,以及为什么我们可以根据它去操作线程级别的变量。但是 ThreadLocal
严格绑定当前线程,不适用于线程切换的情况,比如在分布式 Tracing 中,你无法控制会不会有服务存在线程切换。先看一个例子:
public class InheritableThreadLocalTest1 {
private static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();
public static void main(String[] args) {
HOLDER.set(1);
System.out.println(currentName()+":"+HOLDER.get());
new Thread(()-> System.out.println(currentName()+":"+HOLDER.get())).start();
}
private static String currentName(){
return Thread.currentThread().getName();
}
}
输出结果:
main:1
Thread-0:null
可以发现,在子线程(即 Thread-0
线程)中无法获取父线程(即 main
线程)的数据,用官方的翻译来说的话就是 ThreadLocal
不支持继承。这个也很好理解,虽然都是 Thread
类的实例,但是不同实例之间怎么可能直接获取对方成员变量的数据呢。
一种解决思路
一般我们多线程操作都是会比如实现 Runable,封装一个 Task
类,我们可以提前在 Task
类中传入父线程的变量,示例代码如下:
/**
* @author
* @Description 执行任务包装类
* @Date 创建于 2020-03-29 14:51
*/
public class MyTask implements Runnable {
private Runnable runnable;
private Integer inheritableValue;
@Override
public void run() {
try {
InheritableThreadLocalTest.HOLDER.set(inheritableValue);
runnable.run();
}finally {
InheritableThreadLocalTest.HOLDER.remove();
}
}
public MyTask(Runnable runnable) {
this.runnable = runnable;
this.inheritableValue = InheritableThreadLocalTest.HOLDER.get();
}
}
测试代码:
public class InheritableThreadLocalTest {
public static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();
public static void main(String[] args) {
HOLDER.set(1);
System.out.println(currentName() + ":" + HOLDER.get());
Runnable task = ()-> System.out.println(currentName()+":"+HOLDER.get());
new Thread(task,"simple task").start();
new Thread(new MyTask(task),"inheritable task").start();
}
private static String currentName() {
return Thread.currentThread().getName();
}
}
输出结果:
main:1
simple task:null
inheritable task:1
会发现是可以获取到父线程的值。JDK 提供了 InheritableThreadLocal
解决 ThreadLocal
继承的问题。
InheritableThreadLocal
InheritableThreadLocal
继承了 ThreadLocal
,有这么几个方法:
结合上一篇文章中分析的 ThreadLocal#set
方法,优先看下 createMap
方法:
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
可以发现与 ThreadLocal
不同的是,InheritableThreadLocal
关注的是 Thread
的 inheritableThreadLocals
变量:
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
也是 ThreadLocalMap
类型,咋一看并没有什么特殊的,参考上文我们自己实现的一种解决思路,我们是在子线程执行方法前将父线程的值设置进去的,看看 JDK 是怎么做的,先看 Thread#init
方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
this.name = name;
//获取当前线程
Thread parent = currentThread();
...
//inheritThreadLocals 一般情况都是 true
//如果父线程有可继承的变量,则赋值给当前线程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
后面的 createInheritedMap 方法的细节这里就不过多赘述了,主要积就是将父线程中所有可继承的变量赋值给子线程。可以发现,思路都是一致的,就是提前预存父线程中的数据。
欢迎关注公众号