我们都知道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