TransmittableThreadLocal 使用详解
1 TransmittableThreadLocal 是什么?
TransmittableThreadLocal 是一个 Java 类,它是 ThreadLocal 类的一个扩展。它的作用是在多线程环境下,将某个变量的值从一个线程传递到另一个线程。与普通的 ThreadLocal 不同,TransmittableThreadLocal 可以在线程切换时保持变量的值不变,从而实现线程间的值传递。这对于一些特定的应用场景,如线程池中的线程复用,非常有用。
1.1 引入 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>
1.2 TransmittableThreadLocal 类的大致使用
- 首先,导入 TransmittableThreadLocal 类的包:
import com.alibaba.ttl.TransmittableThreadLocal;
- 创建 TransmittableThreadLocal 对象,并指定泛型类型为要传递的变量类型:
TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
- 在需要传递变量值的线程中,通过 set 方法设置变量的值:
transmittableThreadLocal.set("Hello, World!");
- 在其他线程中,通过 get 方法获取传递的变量值:
String value = transmittableThreadLocal.get();
- 在使用完变量后,应该及时调用 remove 方法来清理对应的变量值,以避免内存泄漏:
transmittableThreadLocal.remove();
需要注意的是,TransmittableThreadLocal 是一个用于在多线程环境下传递值的工具类。它是 InheritableThreadLocal 的一个扩展,可以在线程之间传递值,并且支持线程池等场景。
从线程安全的角度来看,TransmittableThreadLocal 并不是完全线程安全的。尽管它提供了跨线程传递值的功能,但在某些情况下可能会出现线程安全问题。
1.3 TransmittableThreadLocal 在使用过程中需要特别注意以下几点:
对于普通的单线程应用,TransmittableThreadLocal 是线程安全的,因为每个线程都有自己独立的副本。
在多线程环境下,如果多个线程同时修改同一个 TransmittableThreadLocal 实例的值,可能会导致数据混乱或错误的结果。
当使用线程池时,由于线程的重用,可能会导致 TransmittableThreadLocal 值的泄漏或错乱。这是因为线程池中的线程在执行完任务后并不会被销毁,而是被放回线程池中等待下一次任务。如果没有正确清理 TransmittableThreadLocal 的值,那么下次使用该线程时可能会获取到上一次的残留值。虽然 TransmittableThreadLocal 提供了在多线程环境下传递值的功能,但在使用时需要注意线程安全性,并且合理管理和清理 TransmittableThreadLocal 的值,以避免潜在的线程安全问题。
1.4 测试 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());
}
}
}
打印值:
1.5 使用 TransmittableThreadLocal 需要注意的点
- 内存泄漏:TransmittableThreadLocal 使用 ThreadLocalMap 来存储变量值,如果没有正确清理或移除对应的变量值,可能会导致内存泄漏。因此,在使用完变量后,应该及时调用 remove 方法来清理对应的变量值。
- 线程安全性:TransmittableThreadLocal 并不是线程安全的,它仅仅是在同一个线程内保持变量值的传递。因此,在多线程环境下使用时,需要额外的线程同步机制来保证数据的一致性和正确性。
- 对象引用问题:TransmittableThreadLocal 存储的是对象的引用,而不是对象本身。如果存储的是可变对象,需要特别注意对象的修改是否会影响其他线程中的引用。避免在多线程环境下出现意外的数据共享或竞态条件。
- 性能开销:由于 TransmittableThreadLocal 需要在线程切换时传递变量值,可能会引入一定的性能开销。在高并发场景下,需要评估和权衡使用 TransmittableThreadLocal 带来的性能损耗和实际需求之间的关系。
在实际开发中,使用 TransmittableThreadLocal 需要谨慎考虑线程安全性、内存泄漏、对象引用和性能开销等问题,以确保正确且高效地在多线程环境中传递变量值。
1.6 TransmittableThreadLocal 在什么情况下会导致内存泄漏
TransmittableThreadLocal 是一个特殊的 ThreadLocal 实现,它可以在线程间传递值。在某些情况下,如果不正确地使用 TransmittableThreadLocal,可能会导致内存泄漏。以下是可能导致内存泄漏的情况:
- 线程池未正确清理:如果在使用线程池时,没有正确地清理线程中的 TransmittableThreadLocal 值,那么这些值将一直保留在线程中,可能导致内存泄漏。
- 异步场景下的延迟清理:在一些异步场景中,由于线程的生命周期较长,TransmittableThreadLocal 的值可能会一直保留在线程中,直到异步操作完成。如果不及时清理这些值,可能会导致内存泄漏。
- 长时间运行的应用程序:对于长时间运行的应用程序,如果 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:在异步请求开始时被调用,用于通知拦截器该请求已经进入异步处理模式。
上面例子中,就是在异步请求处理前赋值、在请求处理完成后,移除保存的值。
2 TransmittableThreadLocal 和 ThreadLocal 的区别
1、TransmittableThreadLocal 和 ThreadLocal 都用于在每个线程中存储特定的变量值。然而,TransmittableThreadLocal 在 ThreadLocal 的基础上进行了扩展,允许在线程复用或切换时传递或继承变量值。
2、TransmittableThreadLocal 可以在线程之间传递变量值,而 ThreadLocal 仅在当前线程内有效。
3、TransmittableThreadLocal 是 Alibaba TTL 库提供的扩展类,需要引入相应的依赖。而 ThreadLocal 是 Java 标准库的一部分,无需额外的依赖。
2.1 线程之间值传递有哪些方式
1、使用 synchronized 关键字、Lock 接口及其实现类(如 ReentrantLock)、Java 的原子类
2、ThreadLocal:通过 ThreadLocal 类可以为每个线程创建独立的变量副本,从而实现线程间的数据隔离,保证每个线程访问的是自己的变量副本,避免了线程间的数据竞争问题。
3、并发容器:Java 提供了一些并发容器(如 ConcurrentHashMap、ConcurrentLinkedQueue 等),这些容器在多线程环境下提供了线程安全的操作,可以避免多个线程同时修改容器导致的数据不一致问题。
以及 TransmittableThreadLocal,等等这些方式都可以帮助确保多个线程之间值的安全传递,具体使用哪种方式取决于具体的业务需求和场景。
TransmittableThreadLocal 实现原理可参考官网源码说明:阿里巴巴 - 开源项目 / transmittable-thread-local