每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(下)

简介: 每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(下)

那来验证下吧

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();
    }
}

596c07184df94f3c8943196a8e362e44.png


符合预期,在子线程中如愿访问到了在主线程中设置的本地环境变量。


局限性


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());
        }
    }
}

d8ec5e944221433a9453809d3901c517.png


子线程中出现出现了线程本地变量混乱的现象

07acbeba505048bc82f564e28e3773a1.png


TransmittableThreadLocal




e4ee9d8d6b304c5eabd5ce39f2bea63b.png



TransmittableThreadLocal 是什么


TransmittableThreadLocal 是阿里巴巴开源的专门解决 InheritableThreadLocal 的局限性,实现线程本地变量在线程池的执行过程中,能正常的访问父线程设置的线程变量。


实现原理


从InheritableThreadLocal 不支持线程池的根本原因是 InheritableThreadLocal 是在父线程创建子线程时复制的,由于线程池的复用机制,“子线程”只会复制一次。要支持线程池中能访问提交任务线程的本地变量,其实只需要在父线程在向线程池提交任务时复制父线程的上下环境,那在子线程中就能够如愿访问到父线程中的本地遍历,实现本地环境变量在线程调用之中的透传,实现链路跟踪,这也就是 TransmittableThreadLocal 最本质的实现原理。

92fc2343375247f390a1f1a01d87f96f.png


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


de4ff7b677c04eef953a130a3a83ac1b.png

测试

    <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());
        }
    }
}

5562f24d83484f1da1acc66adbe13f9d.png


e301b70c28df4c3392d6397b9870964d.png


参考: 全链路压测必备基础组件之线程上下文管理之“三剑客”

相关文章
|
9月前
|
存储 缓存 安全
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
111 0
|
7月前
|
存储 Java
ThreadLocal应用及理解
ThreadLocal应用及理解
59 10
|
8月前
|
Java
ThreadLocal 场景题
ThreadLocal 场景题
57 1
|
9月前
|
存储 Java 数据管理
ThreadLocal的使用
`ThreadLocal`是Java中的线程局部变量工具,确保每个线程都有自己的变量副本,互不干扰。适用于保持线程安全性数据和跨方法共享数据。基本用法包括创建实例、设置和获取值以及清除值。例如,创建ThreadLocal对象后,使用`.set()`设置值,`.get()`获取值,`.remove()`清除值。注意ThreadLocal可能引起内存泄漏,应适时清理,并谨慎使用以避免影响代码可读性和线程安全性。它是多线程编程中实现线程局部数据管理的有效手段。
106 10
|
9月前
|
存储 Java 编译器
ThreadLocal、InheritThreadLocal、TransmittableThreadLocal
ThreadLocal、InheritThreadLocal、TransmittableThreadLocal
179 0
|
9月前
|
设计模式 缓存 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理3
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
1322 1
|
9月前
|
存储 Java
ThreadLocal 有什么用
ThreadLocal 有什么用
65 0
|
9月前
|
存储 前端开发 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理1
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
2308 0
|
9月前
|
存储 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理2
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
889 0
|
存储 安全 Java
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(上)
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
157 0

热门文章

最新文章