一、前言
在上一篇博文 《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> 运行结果如下:
3> 结果分析:从运行结果结合线程池的运行机制可以看出:
- 第一任务提交到线程池时,线程池中没有核心线程,创建一个线程,此时可以获取到父线程(main线程)中最新的InheritableThreadLocal值;
- 第二个任务提交到线程池时,线程池中核心线程数为1,1 < 2,所以再创建一个线程,此时也可以获取到父线程(main线程)中最新的InheritableThreadLocal值;
- 第三个任务提交到线程池时,线程池中核心线程数为2,线程进入到阻塞队列,等待空闲线程来调度任务。假如此时coreThread1刚执行完任务1,来执行任务3,则此时的InheritableThreadLocal是历史版本的(main thread-1)。
- 第四个任务提交到线程池时,线程池中核心线程数为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())
)
);
}
}
运行结果正常,如下所示:
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
的实现原理之后,再做演示、推论。