14、TransmittableThreadLocal解决线程池中ThreadLocal无法在父子线程之间传递的问题

一、前言

在上一篇博文 《08、InheritableThreadLocal(可继承的ThreadLocal)详解》 中介绍了InheritableThreadLocal如何实现父子线程之间传递ThreadLocal信息;最后提到InheritableThreadLocal的一个问题:在使用线程池时InheritableThreadLocal会完全失效。

二、TransmittableThreadLocal

因为线程池中的线程是会被复用的,所以在使用线程池进行多线程编程时,操作线程本地变量时需要特别注意;下面我们使用TransmittableThreadLocal 解决线程本地变量在线程池之间的传递问题。

1、问题复现

1> 复现案例:
创建一个核心线程数为2,最大线程数为2的线程池,在主线程中每次对线程本地变量赋值之后,提交一个线程任务到线程池中运行,如下:

public class InheritableThreadLocalTest1 {

    public final static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    public static final int CORE_THREAD_SIZE = 2;

    public static ThreadPoolExecutor executorService = new ThreadPoolExecutor(CORE_THREAD_SIZE,
            CORE_THREAD_SIZE,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>());

    public static void main(String[] args) throws InterruptedException {

        inheritableThreadLocal.set("main thread-1");
        executorService.submit(() -> {

            System.out.println("1 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get());
        });

        inheritableThreadLocal.set("main thread-2");
        executorService.submit(() -> {

            System.out.println("2 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get());
        });

        inheritableThreadLocal.set("main thread-3");
        executorService.submit(() -> {

            System.out.println("3 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get());
        });

        inheritableThreadLocal.set("main thread-4");
        executorService.submit(() -> {

            System.out.println("4 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get());
        });

    }
}

2> 运行结果如下:
&nbsp;

3> 结果分析:从运行结果结合线程池的运行机制可以看出:

  1. 第一任务提交到线程池时,线程池中没有核心线程,创建一个线程,此时可以获取到父线程(main线程)中最新的InheritableThreadLocal值;
  2. 第二个任务提交到线程池时,线程池中核心线程数为1,1 < 2,所以再创建一个线程,此时也可以获取到父线程(main线程)中最新的InheritableThreadLocal值;
  3. 第三个任务提交到线程池时,线程池中核心线程数为2,线程进入到阻塞队列,等待空闲线程来调度任务。假如此时coreThread1刚执行完任务1,来执行任务3,则此时的InheritableThreadLocal是历史版本的(main thread-1)。
  4. 第四个任务提交到线程池时,线程池中核心线程数为2,线程进入到阻塞队列,等待空闲线程来调度任务。假如此时coreThread2刚执行完任务2,来执行任务4,则此时的InheritableThreadLocal是历史版本的(main thread-2)。

因此,我们可以确定,只有当核心线程数不足时才会正确的传递的InheritableThreadLocal,而如果线程池中的线程在循环使用了,则不会加载最新的InheritableThreadLocal,而是使用历史上的某个版本数据。

线程池部分内容请见博文:[源码分析ThreadPoolExecutor线程池实现原理][ThreadPoolExecutor]。

2、解决方案

引入阿里提供的TransmittableThreadLocal,github地址为:https://github.com/alibaba/transmittable-thread-local

1)引入maven依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.12.6</version>
</dependency>

2)代码中使用

1> 修改线程本地变量类型为TransmittableThreadLocal

public final static TransmittableThreadLocal<String> inheritableThreadLocal = new TransmittableThreadLocal<>();

其次,官方给定了三种方式保证TransmittableThreadLocal的正确传递:

2.1> 修饰Runnable和Callable
2.2> TtlExecutors 修饰线程池
2.3> 使用Java Agent来修饰JDK线程池实现类

2.1、 修饰Runnable和Callable:
public class TransmittableThreadLocalTest {

    public final static TransmittableThreadLocal<String> inheritableThreadLocal = new TransmittableThreadLocal<>();
    public static final int CORE_THREAD_SIZE = 2;

    public static ThreadPoolExecutor executorService = new ThreadPoolExecutor(CORE_THREAD_SIZE,
            CORE_THREAD_SIZE,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>());

    public static void main(String[] args) throws InterruptedException {

        inheritableThreadLocal.set("main thread-1");
        executorService.submit(TtlRunnable.get(
                        () -> System.out.println("1 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
                )
        );

        inheritableThreadLocal.set("main thread-2");
        executorService.submit(TtlRunnable.get(
                        () -> System.out.println("2 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
                )
        );

        inheritableThreadLocal.set("main thread-3");
        executorService.submit(TtlRunnable.get(
                        () -> System.out.println("3 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
                )
        );

        inheritableThreadLocal.set("main thread-4");
        executorService.submit(TtlRunnable.get(
                        () -> System.out.println("4 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
                )
        );

    }
}

运行结果正常,如下所示:
&nbsp;

2.2、 TtlExecutors 修饰线程池:
public class TransmittableThreadLocalTest {

    public final static TransmittableThreadLocal<String> inheritableThreadLocal = new TransmittableThreadLocal<>();
    public static final int CORE_THREAD_SIZE = 2;

    public static ExecutorService executorService =
            TtlExecutors.getTtlExecutorService(new ThreadPoolExecutor(CORE_THREAD_SIZE,
                    CORE_THREAD_SIZE,
                    10,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>())
            );

    public static void main(String[] args) throws InterruptedException {

        inheritableThreadLocal.set("main thread-1");
        executorService.submit(
                () -> System.out.println("1 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
        );

        inheritableThreadLocal.set("main thread-2");
        executorService.submit(
                () -> System.out.println("2 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
        );

        inheritableThreadLocal.set("main thread-3");
        executorService.submit(
                () -> System.out.println("3 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
        );

        inheritableThreadLocal.set("main thread-4");
        executorService.submit(
                () -> System.out.println("4 obtain inheritableThreadLocal in threadPool: " + inheritableThreadLocal.get())
        );

    }
}

运行结果和上述一致;

2.3、使用Java Agent来修饰JDK线程池实现类;

该种方式我们在后续分析TransmittableThreadLocal的实现原理之后,再做演示、推论。

可以先参考:github <–> 23-使用java-agent来修饰jdk线程池实现类