我们都知道ThreadLocal可以设置本地变量,之后在当前线程中获取。但是如果我们在线程中设置了变量,在子线程需要获取,该如果做呢?
InheritableThreadLocal
InheritableThreadLocal可以实现子线程获取父线程的本地变量,示例代码如下:
private static void inheritableThreadLocalTest() {
InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("主线程设置的值");
new Thread(() -> {
System.out.println("子线程通过InheritableThreadLocal获取主线程的值:" + inheritableThreadLocal.get());
}).start();
System.out.println("主线程通过InheritableThreadLocal获取值:" + inheritableThreadLocal.get());
}
运行程序打印如下:
主线程通过InheritableThreadLocal获取值:主线程设置的值
子线程通过InheritableThreadLocal获取主线程的值:主线程设置的值
需要注意的是:InheritableThreadLocal设置的变量还是传递的引用,如果设置的对象是非基础类型,父线程或者子线程改了对象中的属性的值,那么子线程或者父线程中对象的值同样受影响。(ThreadLocal同样存在这个问题)
这时有两种处理方式,一种是在设置变量的时候用深拷贝复制的对象。另外一种是继承InheritableThreadLocal类,重写childValue方法,实现深拷贝的逻辑。示例代码如下:
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeepCopyInheritableThreadLocal<T> extends InheritableThreadLocal {
@SneakyThrows
@Override
protected Object childValue(Object parentValue) {
ObjectMapper om = new ObjectMapper();
return (T)om.readValue(om.writeValueAsString(parentValue), parentValue.getClass());
}
}
以上我才用的是通过json序列化后再反序列化的方式。
测试代码如下:
private static void deepCopyInheritableThreadLocalTest() throws InterruptedException {
DeepCopyInheritableThreadLocal ditl = new DeepCopyInheritableThreadLocal();
Student stu = new Student("main");
ditl.set(stu);
Thread thread = new Thread(() -> {
Student o = (Student) ditl.get();
System.out.println("子线程通过获取主线程的对象:" + o);
o.setName("child");
System.out.println("子线程修改对象中值为: " + o);
});
thread.start();
thread.join();
System.out.println("主线程获取对象为:" + stu);
}
运行程序,结果如下:
子线程通过获取主线程的对象:Student(name=main)
子线程修改对象中值为: Student(name=child)
主线程获取对象为:Student(name=main)
以上我用的测试代码都是用Thread进行的,但是实际开发中我们用线程池会更合理。
由于InheritableThreadLocal是在子线程创建的时候才会拷贝父线程中的本地变量,之后就不会再次拷贝了。如果使用线程池,想每次提交任务到线程池中的时候,我们都设置本地变量,这时候在子线程中获取父线程本地变量就会有问题。演示代码如下:
private static void threadPoolInheritableThreadLocalTest() throws InterruptedException {
InheritableThreadLocal<Integer> itl = new InheritableThreadLocal();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 5000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1));
for (int i = 0; i < 4; i ++) {
Thread.sleep(1000);
itl.set(i);
System.out.println("主线程变量设置为:" + i);
threadPoolExecutor.execute(new Thread(() -> {
System.out.println("子线程得到变量为:" + itl.get());
}));
}
}
运行程序,结果如下:
主线程变量设置为:0
子线程得到变量为:0
主线程变量设置为:1
子线程得到变量为:1
主线程变量设置为:2
子线程得到变量为:0
主线程变量设置为:3
子线程得到变量为:1
可以看到,我们之后在父线程中设置的变量,由于线程复用,子线程中并没有获取到正确的变量。
那么如果解决这种问题,应该怎么做呢?
TransmittableThreadLocal
使用该类需要引入maven依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.1</version>
</dependency>
示例代码如下:
public static void transmittableThreadLocalTest() throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 5000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1));
TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
ExecutorService pool = TtlExecutors.getTtlExecutorService(threadPoolExecutor);
for (int i = 0; i < 4; i ++) {
Thread.sleep(1000);
threadLocal.set(i);
System.out.println("主线程变量设置为:" + i);
pool.execute(new Thread(() -> {
System.out.println("子线程得到变量为:" + threadLocal.get());
}));
}
}
运行程序,结果:
主线程变量设置为:0
子线程得到变量为:0
主线程变量设置为:1
子线程得到变量为:1
主线程变量设置为:2
子线程得到变量为:2
主线程变量设置为:3
子线程得到变量为:3