ThreadLocal值传递问题

ThreadLocal

ThreadLocal是Thread的局部变量,可以理解为ThreadLocalVariable。它在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

ThreadLocal在不同线程之间值传递

ThreadLocal与当前线程绑定的变量赋值时,那切换到其他线程则获取不到该值,那么下面看看有没有什么解决办法。

现象解决方案描述
同一线程之间传递值ThreadLocal给线程提供一个本地变量,当线程消失的时候,所有的本地示例都会被回收
父子线程之间传递值InheritableThreadLocaljdk提供的类,是ThreadLocal的升级版,解决父子线程之间传递值的问题
线程池不同线程传递值TransmittableThreadLocal简称TTL,是阿里巴巴团队提供的一个框架,主要解决因为存储线程池InheritableThreadLocal失效的问题

ThreadLocal示例

示例一 ThreadLocal线程隔离

ThreadLocal是线程的本地变量,下面一个示例是启了十个线程,每个线程都有一个ThreadLocal,每个线程里初始化值都是100,然后每个线程中去改变变量值+1,最后结果可以看出每个线程都是互不影响的。

public class ThreadLocalMapDemo extends Thread {
    /**
     * ThreadLocal特性:
     * 1、线程并发:在多线程并发场景下使用。
     * 2、传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量。
     * 3、线程隔离:每个线程变量都是独立的,不会相互影响。
     *
     * ThreadLocalMap是ThreadLocal的静态内部类
     * 与HashMap类似,初始容量默认是16,初始容量必须是2的整数幂。通过Entry类的数据table存放数据。size是存放的数量,threshold是扩容阈值。
     * Entry继承自WeakReference,key是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
     * 弱引用:垃圾回收器一旦发现了弱引用的对象,不管内存是否足够,都会回收它的内存。
     */
    private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();
    private Integer value;

    ThreadLocalMapDemo(Integer value) {
        this.value = value;
    }

    @Override
    public void run() {
        // 设置值不会影响其他线程
        THREAD_LOCAL.set(value);
        System.out.println("Thread name = " + currentThread().getName() + " current value = " + THREAD_LOCAL.get());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 重新设置值
        THREAD_LOCAL.set(THREAD_LOCAL.get() + 1);
        System.out.println("Thread name = " + currentThread().getName() + " current value after change is = " + THREAD_LOCAL.get());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            ThreadLocalMapDemo demo = new ThreadLocalMapDemo(100);
            demo.setName("线程" + i);
            demo.start();
        }
    }
}

示例二

首先,定义一个ThreadLocal工具类,

/**
 * ThreadLocal工具类
 *
 * @param <T> 范型T
 */
public final class ThreadLocalUtil<T> {
    private final ThreadLocal<T> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 无参构造函数
     */
    public ThreadLocalUtil() {
    }

    /**
     * 有参构造函数
     *
     * @param t
     */
    ThreadLocalUtil(T t) {
        THREAD_LOCAL.set(t);
    }

    /**
     * 从ThreadLocalMap中获取当前线程变量
     *
     * @return 变量值
     */
    public T getValue() {
        return THREAD_LOCAL.get();
    }

    /**
     * ThreadLocalMap中写入当前线程变量
     *
     * @param t 入参
     * @return 当前写入值
     */
    public T setValue(T t) {
        THREAD_LOCAL.set(t);
        return t;
    }

    /**
     * 从ThreadLocalMap中删除当前线程变量
     */
    public void removeValue() {
        THREAD_LOCAL.remove();
    }
}

主线程中定义一个静态变量util,初始值是123,在子线程和主线程中分别打印ThreadLocal变量值。

public class ThreadLocalExample {
    private static Logger logger = LoggerFactory.getLogger(ThreadLocalExample.class);
    private static ThreadLocalUtil<Integer> util = new ThreadLocalUtil<>(123);

    public static void main(String[] args) {
        new Thread(() -> logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue())).start();
        logger.info("Thread name = {}, thread variable is {}", Thread.currentThread().getName(), util.getValue());
    }
}

示例结果如下,子线程输出的是子线程初始值null,主线程输出的是初始值123。另外,这里不保证线程的有序性,只是简易示例。
在这里插入图片描述

InheritableThreadLocal(ITL)示例

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量时使用。
把上面的代码稍微改造一下,

/**
 * ThreadLocal工具类
 *
 * @param <T> 范型T
 */
public final class ThreadLocalUtil<T> {
    private final ThreadLocal<T> THREAD_LOCAL = new InheritableThreadLocal<>();

    /**
     * 无参构造函数
     */
    public ThreadLocalUtil() {
    }

    /**
     * 有参构造函数
     *
     * @param t
     */
    ThreadLocalUtil(T t) {
        THREAD_LOCAL.set(t);
    }

    /**
     * 从ThreadLocalMap中获取当前线程变量
     *
     * @return 变量值
     */
    public T getValue() {
        return THREAD_LOCAL.get();
    }

    /**
     * ThreadLocalMap中写入当前线程变量
     *
     * @param t 入参
     * @return 当前写入值
     */
    public T setValue(T t) {
        THREAD_LOCAL.set(t);
        return t;
    }

    /**
     * 从ThreadLocalMap中删除当前线程变量
     */
    public void removeValue() {
        THREAD_LOCAL.remove();
    }
}

代码不变,子线程中输出的ThreadLocal变量继承了主线程中的ThreadLocal变量值,
在这里插入图片描述

TransmittableThreadLocal(TTL)示例

TransmittableThreadLocal,简称TTL,它不同于ITL,它能实现池化线程间传递。直接看官方的时序图,
在这里插入图片描述
大部分过程都依赖于 TransmittableThreadLocal 或 TransmittableThreadLocal 中声明的静态工具类 Transmitter 。Transmitter 主要负责 ThreadLocal 的管理和值的传递。

使用前需要导入依赖,

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

下面启了两个线程main_01和main_02作为父线程,线程池中执行的三个子线程拿到父线程设置的ttl变量,父线程再修改ttl值,然后线程池中的子线程继续读取ttl中的值。

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TransmittableThreadLocalDemo {
    /**
     * 需要注意的是,使用TTL的时候,要想传递的值不出问题,线程池必须得用TTL加一层代理
     */
    private static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
    private static ThreadLocal<Integer> ttl = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            String mainThreadName = "main_01";
            ttl.set(1);
            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(1), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            sleep(1L); //确保上面的会在tl.set执行之前执行
            ttl.set(2); // 等上面的线程池第一次启用完了,父线程再给自己赋值

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(2), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));

        }).start();


        new Thread(() -> {

            String mainThreadName = "main_02";

            ttl.set(3);

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之前(3), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            sleep(1L); //确保上面的会在tl.set执行之前执行
            ttl.set(4); // 等上面的线程池第一次启用完了,父线程再给自己赋值

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            executorService.execute(() -> {
                sleep(1L);
                System.out.println(String.format("本地变量改变之后(4), 父线程名称-%s, 子线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), ttl.get()));
            });

            System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), ttl.get()));

        }).start();

    }

    private static void sleep(long longTime) {
        try {
            Thread.sleep(longTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最终结果如下,线程池中子线程拿到的是父线程中设置的值。
在这里插入图片描述

总结

ThreadLocal 的使用,本身类似于全局变量,而且是可修改的。一旦中间过程被修改,就无法保证整体流程的前后一致性。

应该尽量避免在业务代码中使用的。
Do not use, only when you know why.

参考链接:
1、https://github.com/alibaba/transmittable-thread-local
2、https://zhuanlan.zhihu.com/p/146124826

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Feign 是一个基于 Java 的 HTTP 客户端库,用于与 RESTful 服务进行交互。在 Feign 中,如果需要在请求之间传递数据,通常不推荐直接使用 ThreadLocal,因为 Feign 的请求处理是异步的,每个请求在单独的线程中执行。然而,如果你确实遇到了某种场景需要跨请求共享信息,比如认证令牌,可以考虑以下方法: 1. **使用装饰者模式**:你可以创建一个自定义的 `Interceptor`(拦截器),在每个请求开始前设置 ThreadLocal,然后在拦截器链的回调中获取这个值。 ```java public class MyInterceptor implements ClientRequestInterceptor { private final ThreadLocal<String> threadLocal; public MyInterceptor(String value) { threadLocal = new ThreadLocal<>(); threadLocal.set(value); } @Override public void apply(RequestTemplate requestTemplate) { // 在这里添加你需要的操作,如设置请求头等 // requestTemplate.header("Authorization", threadLocal.get()); } } ``` 使用时,将 `MyInterceptor` 添加到 Feign client 的拦截器列表中: ```java Feign.builder() .client(new OkHttpClient()) .interceptors(Arrays.asList(new MyInterceptor("your_value"))) .target(MyService.class, "http://example.com"); ``` 2. **使用响应拦截器**:对于返回的响应,你也可以设置一个全局的响应拦截器来处理 ThreadLocal 的值。 ```java public class ResponseInterceptor implements ClientResponseInterceptor { @Override public ClientResponse intercept(ClientRequest originalRequest, byte[] originalBody, Response response) { String value = threadLocal.get(); // 对响应进行处理,例如修改响应体或添加头部 return new ClientResponse(response.request(), response.response(), response.headers(), originalBody, new ByteArrayInputStream(response.body())); } } ``` 然后添加到客户端配置: ```java Feign.builder() .client(new OkHttpClient()) .responseInterceptors(Arrays.asList(new ResponseInterceptor())) .target(MyService.class, "http://example.com"); ``` 但是需要注意的是,这种做法可能会导致代码难以理解和维护,因为它绕过了 Feign 的初衷和设计。在大多数情况下,应该优先考虑使用标准的参数传递方式,如请求头、URL 查询参数或请求体。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值