Terry

Terry

TransmittableThreadLocal 使用

291
2024-04-16

1. TransmittableThreadLocal 是什么?

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

1.1 引入 TransmittableThreadLocal 依赖

TransmittableThreadLocal 并不是 Java 标准库的一部分,而是由阿里巴巴开源的一个工具库——TransmittableThreadLocal(TTL)提供的。因此,需要在项目的构建工具(如 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();
    

1.3 注意事项

  • 线程安全问题

    • 在单线程应用中,TransmittableThreadLocal 是线程安全的,因为每个线程都有自己独立的副本。
    • 在多线程环境下,如果多个线程同时修改同一个 TransmittableThreadLocal 实例的值,可能会导致数据混乱或错误的结果。
  • 线程池中的值泄漏
    当使用线程池时,由于线程的重用,可能会导致 TransmittableThreadLocal 值的泄漏或错乱。这是因为线程池中的线程在执行完任务后并不会被销毁,而是被放回线程池中等待下一次任务。如果没有正确清理 TransmittableThreadLocal 的值,那么下次使用该线程时可能会获取到上一次的残留值。

1.4 测试 Demo

以下是一个简单的测试示例,演示了在多线程环境下,线程切换后数值的打印信息。

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

输出结果:
|800

1.5 使用 TransmittableThreadLocal 需要注意的点

  1. 内存泄漏
    TransmittableThreadLocal 使用 ThreadLocalMap 来存储变量值。如果没有正确清理或移除对应的变量值,可能会导致内存泄漏。因此,在使用完变量后,应该及时调用 remove 方法来清理对应的变量值。

  2. 线程安全性
    TransmittableThreadLocal 并不是完全线程安全的,它仅仅是在同一个线程内保持变量值的传递。在多线程环境下使用时,需要额外的线程同步机制来保证数据的一致性和正确性。

  3. 对象引用问题
    TransmittableThreadLocal 存储的是对象的引用,而不是对象本身。如果存储的是可变对象,需要特别注意对象的修改是否会影响其他线程中的引用,避免在多线程环境下出现意外的数据共享或竞态条件。

  4. 性能开销
    由于 TransmittableThreadLocal 需要在线程切换时传递变量值,可能会引入一定的性能开销。在高并发场景下,需要评估和权衡使用 TransmittableThreadLocal 带来的性能损耗和实际需求之间的关系。

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

以下是可能导致内存泄漏的情况:

  1. 线程池未正确清理
    如果在使用线程池时,没有正确地清理线程中的 TransmittableThreadLocal 值,这些值将一直保留在线程中,可能导致内存泄漏。

  2. 异步场景下的延迟清理
    在一些异步场景中,由于线程的生命周期较长,TransmittableThreadLocal 的值可能会一直保留在线程中,直到异步操作完成。如果不及时清理这些值,可能会导致内存泄漏。

  3. 长时间运行的应用程序
    对于长时间运行的应用程序,如果 TransmittableThreadLocal 的值一直存储在线程中而不被清理,可能会导致内存泄漏。

解决方案:
在适当的时候手动清理 TransmittableThreadLocal 的值,确保它们不会一直保留在线程中。可以在使用完值后调用 remove 方法,或者使用 try-finally 块确保正确清理。

1.7 内存泄漏和内存溢出的区别

  • 内存泄漏(Memory Leak)
    指的是在程序中动态分配的内存空间在不再使用时没有被正确释放或回收的情况。这会导致这些内存空间一直被占用,无法被其他部分或其他程序使用,从而导致内存浪费。

  • 内存溢出(Memory Overflow)
    指的是程序在申请内存时,需要的内存超出了系统所能提供的可用内存大小。当程序尝试分配更多的内存空间时,由于没有足够的内存可供分配,就会产生内存溢出的错误。

2. TransmittableThreadLocal 和 ThreadLocal 的区别

特性ThreadLocalTransmittableThreadLocal
作用范围仅在当前线程内有效可以在线程之间传递变量值
线程切换支持不支持支持
依赖Java 标准库需要引入 Alibaba TTL 库

2.1 线程之间值传递的方式

  1. 同步机制
    使用 synchronized 关键字、Lock 接口及其实现类(如 ReentrantLock)、Java 的原子类等。

  2. ThreadLocal
    通过 ThreadLocal 类可以为每个线程创建独立的变量副本,从而实现线程间的数据隔离。

  3. 并发容器
    Java 提供了一些并发容器(如 ConcurrentHashMapConcurrentLinkedQueue 等),这些容器在多线程环境下提供了线程安全的操作。

  4. TransmittableThreadLocal
    允许在线程复用或切换时传递或继承变量值。

具体使用哪种方式取决于业务需求和场景。

参考资料