那来验证下吧
public class InheritableThreadLocalTest { private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { InheritableThreadLocalTest threadLocalTest = new InheritableThreadLocalTest(); threadLocal.set("artisan InheritableThreadLocal"); doSomething(); } private static void doSomething() { System.out.println("threadLocal中的对象:" + threadLocal.get()); new Thread(()->{ System.out.println("开启子线程"); System.out.println("子线程中获取threadLocal:" + threadLocal.get()); }).start(); } }
符合预期,在子线程中如愿访问到了在主线程中设置的本地环境变量。
局限性
InheritableThreadLocal 支持子线程访问在父线程的核心思想是在创建线程的时候将父线程中的本地变量值复制到子线程,即复制的时机为创建子线程时。
线程池能够复用线程,减少线程的频繁创建与销毁,如果使用 InheritableThreadLocal,那么线程池中的线程拷贝的数据来自于第一个提交任务的外部线程,即后面的外部线程向线程池中提交任务时,子线程访问的本地变量都来源于第一个外部线程,造成线程本地变量混乱
看个代码
package com.artisan.threadlocal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Service { /** * 模拟tomcat线程池 */ private static ExecutorService tomcatExecutors = Executors.newFixedThreadPool(10); /** * 业务线程池,默认Control中异步任务执行线程池 */ private static ExecutorService businessExecutors = Executors.newFixedThreadPool(5); /** * 线程上下文环境,模拟在Control这一层,设置环境变量,然后在这里提交一个异步任务,模拟在子线程中,是否可以访问到刚设置的环境变量值。 */ private static InheritableThreadLocal<Integer> requestIdThreadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { for(int i = 0; i < 10; i ++ ) { // 模式10个请求,每个请求执行ControlThread的逻辑,其具体实现就是,先输出父线程的名称, 然后设置本地环境变量,并将父线程名称传入到子线程中,在子线程中尝试获取在父线程中的设置的环境变量 tomcatExecutors.submit(new ControlThread(i)); } //简单粗暴的关闭线程池 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } businessExecutors.shutdown(); tomcatExecutors.shutdown(); } /** * 模拟Control任务 */ static class ControlThread implements Runnable { private int i; public ControlThread(int i) { this.i = i; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + i); requestIdThreadLocal.set(i); //使用线程池异步处理任务 businessExecutors.submit(new BusinessTask(Thread.currentThread().getName())); } } /** * 业务任务,主要是模拟在Control控制层,提交任务到线程池执行 */ static class BusinessTask implements Runnable { private String parentThreadName; public BusinessTask(String parentThreadName) { this.parentThreadName = parentThreadName; } @Override public void run() { //如果与上面的能对应上来,则说明正确,否则失败 System.out.println("parentThreadName:" + parentThreadName + ":" + requestIdThreadLocal.get()); } } }
子线程中出现出现了线程本地变量混乱的现象
TransmittableThreadLocal
TransmittableThreadLocal 是什么
TransmittableThreadLocal 是阿里巴巴开源的专门解决 InheritableThreadLocal 的局限性,实现线程本地变量在线程池的执行过程中,能正常的访问父线程设置的线程变量。
实现原理
从InheritableThreadLocal 不支持线程池的根本原因是 InheritableThreadLocal 是在父线程创建子线程时复制的,由于线程池的复用机制,“子线程”只会复制一次。要支持线程池中能访问提交任务线程的本地变量,其实只需要在父线程在向线程池提交任务时复制父线程的上下环境,那在子线程中就能够如愿访问到父线程中的本地遍历,实现本地环境变量在线程调用之中的透传,实现链路跟踪,这也就是 TransmittableThreadLocal 最本质的实现原理。
TransmittableThreadLocal 继承自 InheritableThreadLocal,接下来将从 set 方法为入口,开始探究TransmittableThreadLocal 实现原理
@Override public final void set(T value) { // 首先调用父类的 set 方法,将 value 存入线程本地遍历,即 Thread 对象的inheritableThreadLocals 中 super.set(value); // may set null to remove value 如果 value 为空,则调用 removeValue() 否则调用 addValue。 if (null == value) removeValue(); else addValue(); }
private void addValue() { // 当前线程在调用 threadLocal 方法的 set 方法(即向线程本地遍历存储数据时),如果需要设置的值不为 null,则调用 addValue 方法,将当前 ThreadLocal 存储到 TransmittableThreadLocal 的全局静态变量 holder。 if (!holder.get().containsKey(this)) { holder.get().put(this, null); // WeakHashMap supports null value. } }
private void removeValue() { holder.get().remove(this); }
看看holder
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() { @Override protected Map<TransmittableThreadLocal<?>, ?> initialValue() { return new WeakHashMap<TransmittableThreadLocal<?>, Object>(); } @Override protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) { return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue); } };
从中可以看出,使用了线程本地变量,内部存放的结构为 Map<TransmittableThreadLocal<?>, ?>,即该对象缓存了线程执行过程中所有的 TransmittableThreadLocal 对象,并且其关联的值不为空
https://github.com/alibaba/transmittable-thread-local
测试
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.10.2</version> </dependency>
package com.artisan.threadlocal; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.TtlExecutors; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TTLTest { /** * 模拟tomcat线程池 */ private static ExecutorService tomcatExecutors = Executors.newFixedThreadPool(10); /** * 业务线程池,默认Control中异步任务执行线程池 */ private static ExecutorService businessExecutors = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4)); // 使用ttl线程池,该框架的使用,请查阅官方文档。 /** * 线程上下文环境,模拟在Control这一层,设置环境变量,然后在这里提交一个异步任务,模拟在子线程中,是否可以访问到刚设置的环境变量值。 */ private static TransmittableThreadLocal<Integer> requestIdThreadLocal = new TransmittableThreadLocal<>(); // private static InheritableThreadLocal<Integer> requestIdThreadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { for(int i = 0; i < 10; i ++ ) { tomcatExecutors.submit(new ControlThread(i)); } //简单粗暴的关闭线程池 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } businessExecutors.shutdown(); tomcatExecutors.shutdown(); } /** * 模拟Control任务 */ static class ControlThread implements Runnable { private int i; public ControlThread(int i) { this.i = i; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + i); requestIdThreadLocal.set(i); //使用线程池异步处理任务 businessExecutors.submit(new BusinessTask(Thread.currentThread().getName())); } } /** * 业务任务,主要是模拟在Control控制层,提交任务到线程池执行 */ static class BusinessTask implements Runnable { private String parentThreadName; public BusinessTask(String parentThreadName) { this.parentThreadName = parentThreadName; } @Override public void run() { //如果与上面的能对应上来,则说明正确,否则失败 System.out.println("parentThreadName:" + parentThreadName + ":" + requestIdThreadLocal.get()); } } }