TransmittableThreadLocal 使用详解、使用 TransmittableThreadLocal 解决在多线程环境下,线程切换时无法传递变量值的问题

本文介绍了TransmittableThreadLocal在Java中的使用,它是ThreadLocal的扩展,支持跨线程传递值。文章详细讲解了如何引入依赖、使用方法、注意事项,以及内存泄漏和线程安全问题。
摘要由CSDN通过智能技术生成

TransmittableThreadLocal 是什么?

TransmittableThreadLocal 是一个 Java 类,它是 ThreadLocal 类的一个扩展。它的作用是在多线程环境下,将某个变量的值从一个线程传递到另一个线程。与普通的 ThreadLocal 不同,TransmittableThreadLocal 可以在线程切换时保持变量的值不变,从而实现线程间的值传递。这对于一些特定的应用场景,如线程池中的线程复用,非常有用。

引入TransmittableThreadLocal依赖

使用TransmittableThreadLocal 类需要引入相关的依赖。 TransmittableThreadLocal 不是 Java 标准库的一部分,而是由 Alibaba 开源的一个工具库——TransmittableThreadLocal(TTL)提供的。因此,为了在项目中使用 TransmittableThreadLocal 类,需要在项目的构建工具(如 Maven 或 Gradle)的配置文件中添加相应的依赖项。

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

TransmittableThreadLocal类的大致使用

1. 首先,导入 TransmittableThreadLocal 类的包:

import com.alibaba.ttl.TransmittableThreadLocal;

2. 创建 TransmittableThreadLocal 对象,并指定泛型类型为要传递的变量类型:

TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();

3. 在需要传递变量值的线程中,通过 set 方法设置变量的值:

transmittableThreadLocal.set("Hello, World!");

4. 在其他线程中,通过 get 方法获取传递的变量值:

String value = transmittableThreadLocal.get();

5. 在使用完变量后,应该及时调用 remove 方法来清理对应的变量值,以避免内存泄漏:

transmittableThreadLocal.remove();

需要注意的是,TransmittableThreadLocal 是一个用于在多线程环境下传递值的工具类。它是 InheritableThreadLocal 的一个扩展,可以在线程之间传递值,并且支持线程池等场景。

从线程安全的角度来看,TransmittableThreadLocal 并不是完全线程安全的。尽管它提供了跨线程传递值的功能,但在某些情况下可能会出现线程安全问题。

TransmittableThreadLocal 在使用过程中需要特别注意以下几点:

对于普通的单线程应用,TransmittableThreadLocal 是线程安全的,因为每个线程都有自己独立的副本。

在多线程环境下,如果多个线程同时修改同一个 TransmittableThreadLocal 实例的值,可能会导致数据混乱或错误的结果。

当使用线程池时,由于线程的重用,可能会导致 TransmittableThreadLocal 值的泄漏或错乱。这是因为线程池中的线程在执行完任务后并不会被销毁,而是被放回线程池中等待下一次任务。如果没有正确清理 TransmittableThreadLocal 的值,那么下次使用该线程时可能会获取到上一次的残留值。虽然 TransmittableThreadLocal 提供了在多线程环境下传递值的功能,但在使用时需要注意线程安全性,并且合理管理和清理 TransmittableThreadLocal 的值,以避免潜在的线程安全问题。

测试Demo

创建3个线程、测试在多线程环境下,线程切换后数值的打印信息

import cn.hutool.core.util.RandomUtil;
import com.alibaba.ttl.TransmittableThreadLocal;

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

/**
 * @Author Admin
 * @Date 2023/8/23 17:53
 * @Description TransmittableThreadLocal 测试类
 **/
public class TransmittableThreadLocalTest {

    // 创建一个TransmittableThreadLocal对象
    private static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 创建三个任务并提交给线程池
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        // 关闭线程池
        executorService.shutdown();
    }

    // 自定义任务类
    static class Task implements Runnable {
        private int taskId;

        public Task(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {
            // 生成一个随机数
            int randomNumber = RandomUtil.randomInt(100);

            // 将随机数存储到TransmittableThreadLocal中
            threadLocal.set(randomNumber);

            // 打印当前线程ID和随机数
            System.out.println("线程ID: " + Thread.currentThread().getId() + ", 任务ID: " + taskId + ", 随机数值: " + threadLocal.get());

            // 模拟一些计算操作
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("<===============计算完成===============>");

            // 打印当前线程ID和随机数
            System.out.println("线程ID: " + Thread.currentThread().getId() + ", 任务ID: " + taskId + ", 随机数值: " + threadLocal.get());
        }
    }
}

打印值:

 使用 TransmittableThreadLocal 需要注意的点

1. 内存泄漏:TransmittableThreadLocal 使用 ThreadLocalMap 来存储变量值,如果没有正确清理或移除对应的变量值,可能会导致内存泄漏。因此,在使用完变量后,应该及时调用 remove 方法来清理对应的变量值。 
 
2. 线程安全性:TransmittableThreadLocal 并不是线程安全的,它仅仅是在同一个线程内保持变量值的传递。因此,在多线程环境下使用时,需要额外的线程同步机制来保证数据的一致性和正确性。 
 
3. 对象引用问题:TransmittableThreadLocal 存储的是对象的引用,而不是对象本身。如果存储的是可变对象,需要特别注意对象的修改是否会影响其他线程中的引用。避免在多线程环境下出现意外的数据共享或竞态条件。 
 
4. 性能开销:由于 TransmittableThreadLocal 需要在线程切换时传递变量值,可能会引入一定的性能开销。在高并发场景下,需要评估和权衡使用 TransmittableThreadLocal 带来的性能损耗和实际需求之间的关系。 
 
在实际开发中,使用 TransmittableThreadLocal 需要谨慎考虑线程安全性、内存泄漏、对象引用和性能开销等问题,以确保正确且高效地在多线程环境中传递变量值。

TransmittableThreadLocal在什么情况下会导致内存泄漏

TransmittableThreadLocal是一个特殊的ThreadLocal实现,它可以在线程间传递值。在某些情况下,如果不正确地使用TransmittableThreadLocal,可能会导致内存泄漏。以下是可能导致内存泄漏的情况: 
 
1. 线程池未正确清理:如果在使用线程池时,没有正确地清理线程中的TransmittableThreadLocal值,那么这些值将一直保留在线程中,可能导致内存泄漏。 
 
2. 异步场景下的延迟清理:在一些异步场景中,由于线程的生命周期较长,TransmittableThreadLocal的值可能会一直保留在线程中,直到异步操作完成。如果不及时清理这些值,可能会导致内存泄漏。 
 
3. 长时间运行的应用程序:对于长时间运行的应用程序,如果TransmittableThreadLocal的值一直存储在线程中而不被清理,可能会导致内存泄漏。 
 
为了避免这些问题,应该在适当的时候手动清理TransmittableThreadLocal的值,确保它们不会一直保留在线程中。可以在使用完值后调用remove方法或者使用try-finally块确保正确清理。

《内存泄漏和内存溢出的区别?》
内存泄漏(Memory Leak)指的是在程序中动态分配的内存空间在不再使用时没有被正确释放或回收的情况。这意味着这些内存空间将一直被程序占用,无法被其他部分或其他程序使用,从而导致内存的浪费。随着内存泄漏的发生次数增加,可用内存逐渐减少,最终可能导致系统性能下降甚至崩溃。 
 
而内存溢出(Memory Overflow)指的是程序在申请内存时,需要的内存超出了系统所能提供的可用内存大小。当程序尝试分配更多的内存空间时,由于没有足够的内存可供分配,就会产生内存溢出的错误。这种情况通常会导致程序异常终止或崩溃。 
 
简单的说,内存泄漏是指未释放或回收不再使用的内存空间,导致内存的浪费,而内存溢出是指程序需要的内存超过了系统可用内存的大小,导致程序无法继续执行。

举例说明TransmittableThreadLocal类remove方法的调用

public class HeaderInterceptor implements AsyncHandlerInterceptor
{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        if (!(handler instanceof HandlerMethod))
        {
            return true;
        }

        SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));
        SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
        SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));

        String token = SecurityUtils.getToken();
        if (StringUtils.isNotEmpty(token))
        {
            LoginUser loginUser = AuthUtil.getLoginUser(token);
            if (StringUtils.isNotNull(loginUser))
            {
                AuthUtil.verifyLoginUserExpire(loginUser);
                SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception
    {
        SecurityContextHolder.remove();
    }
}

AsyncHandlerInterceptor接口定义了以下方法: 

  • 1、preHandle:在异步请求处理之前被调用。可以用于进行一些预处理操作,如身份验证、参数验证等。该方法返回一个布尔值,用于指示是否继续处理该请求。 
  • 2、postHandle:在异步请求处理完成后被调用。可以对响应进行一些后处理操作,如添加额外的响应头、修改响应内容等。 
  • 3、afterCompletion:在整个请求处理完成后被调用,包括异步请求的处理和响应的返回。可以进行一些资源的清理操作,如释放数据库连接、关闭文件流等。 
  • 4、afterConcurrentHandlingStarted:在异步请求开始时被调用,用于通知拦截器该请求已经进入异步处理模式。 

上面例子中,就是在异步请求处理前赋值、在请求处理完成后,移除保存的值。

TransmittableThreadLocal 和 ThreadLocal 的区别

1、TransmittableThreadLocal 和 ThreadLocal 都用于在每个线程中存储特定的变量值。然而,TransmittableThreadLocal 在 ThreadLocal 的基础上进行了扩展,允许在线程复用或切换时传递或继承变量值。 
2、TransmittableThreadLocal 可以在线程之间传递变量值,而 ThreadLocal 仅在当前线程内有效。 
3、TransmittableThreadLocal 是 Alibaba TTL 库提供的扩展类,需要引入相应的依赖。而 ThreadLocal 是 Java 标准库的一部分,无需额外的依赖。 

线程之间值传递有哪些方式

1、使用synchronized关键字、Lock接口及其实现类(如ReentrantLock)、Java的原子类

2、ThreadLocal:通过ThreadLocal类可以为每个线程创建独立的变量副本,从而实现线程间的数据隔离,保证每个线程访问的是自己的变量副本,避免了线程间的数据竞争问题。

3、并发容器:Java提供了一些并发容器(如ConcurrentHashMap、ConcurrentLinkedQueue等),这些容器在多线程环境下提供了线程安全的操作,可以避免多个线程同时修改容器导致的数据不一致问题。

以及TransmittableThreadLocal,等等这些方式都可以帮助确保多个线程之间值的安全传递,具体使用哪种方式取决于具体的业务需求和场景。

TransmittableThreadLocal 实现原理

可参考官网源码说明:

阿里巴巴-开源项目 / transmittable-thread-local

 

 

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocalJava中的一个类,它提供了一种在多线程环境下保持线程本地变量的机制。每个线程都有自己的ThreadLocal变量副本,线程之间不会相互干扰或混淆数据。 ThreadLocal在每个线程中赋值和销毁的位置是由线程自身控制的。通常情况下,在线程开始之前或者在线程结束之后,我们可以在相应的位置进行赋值和销毁操作。 在多线程环境中,可以通过ThreadLocal的set方法将需要保存的变量值存储在当前线程ThreadLocalMap对象中,这个ThreadLocalMap对象是Thread类的一个成员变量。在需要使用这些变量值,可以通过ThreadLocal的get方法获取到对应的值。 在具体的代码中,可以在每个线程中创建一个ThreadLocal对象,并在需要的使用set方法进行赋值,在需要获取值的地方使用get方法获取对应的值。在线程结束之后,可以通过调用ThreadLocal的remove方法来销毁这个线程ThreadLocal变量。 值得注意的是,必须在合适的机调用remove方法来避免内存泄漏的问题。如果不及调用remove方法,可能会导致ThreadLocal对象持有过多的引用,从而导致内存泄漏的问题。 总结起来,在多线程环境下,ThreadLocal可以在每个线程中赋值并保存需要的变量值,在需要获取值的地方使用get方法来获取对应的值,并在合适的机调用remove方法来销毁ThreadLocal变量,避免内存泄漏的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [多线程2一ThreadLocal源代码分析](https://blog.csdn.net/qq_39494996/article/details/103225063)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ThreadLocal原理以及用法详解](https://blog.csdn.net/weixin_43888891/article/details/119571259)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值