ThreadLocal 用法及应用场景
ThreadLocal 的使用场景
线程局部变量(是 Thread 里面的一个局部变量)。 给线程中一个本地变量的副本提供索引,ThreadLocal 可用来维护和当前线程相关的上下文,而且不需要通过每个方法调用将其作为参数进行传递。
ThreadLocal 原理
ThreadLocal 本质上属于以空间换时间,每个 Thread 中,都维护了一个以开放定址法实现的 ThreadLocal.ThreadLocalMap,从而将数据隔离,实现数据不共享,因此就没有线程安全的问题。
四个方法
| 作用域 | 类型 | 方法 | 描述 |
|---|---|---|---|
| public | T | get() | 返回该线程局部变量的当前线程副本中的值 |
| protected | T | initialValue() | 返回该线程局部变量的当前线程的"初始值" |
| public | void | remove() | 移除该线程局部变量当前线程的值 |
| public | void | set(T value) | 将该线程局部变量的当前线程副本中的值设置为指定值 |
注意事项:
- 注意内存泄漏问题,使用之后一定要调用
remove() - 避免不必要地调用
remove()加快回收速度 - initialValue 方法会被多次调用的情况, 例如:显式调用
remove()后再次get() - Map 中键为线程对象,值为变量副本
ThreadLocal 的用法
- 定义 UID 随机整数并初始化
- 通过 ThreadLocal 保存线程私有 ID
- 每个线程独立维护自己的计数器
- 线程之间互不影响,保证序列号唯一性
java
public class ThreadLocal {
private static final AtomicInteger uId = new AtomicInteger(0);
private static final ThreadLocal<Integer> uNum =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return uId.getAndIncrement();
}
};
public static int getId() {
uNum.set(uNum.get() + 1);
return uNum.get();
}
public static void main(String[] args) {
ThreadLocal uThreadId = new ThreadLocal();
ThreadTask t1 = new ThreadTask<ThreadLocal>("thread-1", uThreadId);
ThreadTask t2 = new ThreadTask<ThreadLocal>("thread-2", uThreadId);
ThreadTask t3 = new ThreadTask<ThreadLocal>("thread-3", uThreadId);
t1.start();
t2.start();
t3.start();
}
}InheritableThreadLocal
InheritableThreadLocalMap 的核心就是在创建子线程时,将父线程的 ThreadLocal 值复制到子线程,实现了线程间值的传递,但需要注意内存管理和线程池中的值污染问题。
核心概念
- ThreadLocal:每个线程都有自己独立的变量副本,线程之间互不干扰。
- InheritableThreadLocal:允许子线程继承父线程的线程局部变量。
适用场景
- 传递上下文信息:如 traceId、用户信息等
- 线程池中的上下文传递(需谨慎处理)
- 分布式跟踪系统
- 多级调用链的上下文传递
java
public class InheritableThreadLocalDemo {
private static final InheritableThreadLocal<String> threadLocal =
new InheritableThreadLocal<String>() {
@Override
protected String childValue(String parentValue) {
// 可以对继承的值进行转换
return "Child inherits: " + parentValue;
}
};
public static void main(String[] args) {
threadLocal.set("Parent Value");
Thread childThread = new Thread(() -> {
System.out.println("Child thread value: " + threadLocal.get());
// 子线程修改不影响父线程
threadLocal.set("Child Modified Value");
System.out.println("Child thread after modification: " + threadLocal.get());
});
childThread.start();
try {
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Parent thread value after child modification: " +
threadLocal.get()); // 仍然是 "Parent Value"
}
}java
public class UniRequestContext {
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<>();
//设计一个set方法
public static void set(Object key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
if (value == null) {
resources.get().remove(key);
}
resources.get().put(key, value);
}
public static Long getUserId() {
Object userId = get("USER_ID");
return userId == null ? null : (Long) userId;
}
//设计一个get方法
public static Object get(Object key) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
return resources.get().get(key);
}
// 设计一个 clear 方法,防止内存泄漏,springboot-web 容器处理请求,tomcat,工作线程会去处理我们的业务请求,工作线程是会长时间存在的,
public static void clear() {
resources.remove();
}
// 实现父子线程之间的线程本地变量传递
// A-->threadLocal ("userId",1001)
// A-->new Thread(B) -->B 线程属于A线程的子线程,threadLocal get("userId")
private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {
@Override
protected Map<Object, Object> initialValue() {
return new HashMap();
}
@Override
protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
if (parentValue != null) {
return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();
} else {
return null;
}
}
}
}最佳实践
- 及时清理:使用完后调用 remove() 避免内存泄漏
- 避免存储大对象:ThreadLocalMap 会一直持有引用
- 线程池谨慎使用:考虑使用 TransmittableThreadLocal
- 合理设计继承策略:重写 childValue() 方法控制继承行为
解决方案:TransmittableThreadLocal
阿里巴巴的 TransmittableThreadLocal 解决了线程池中的传递问题
java
public class TransmittableThreadLocalDemo {
private static final TransmittableThreadLocal<String> context =
new TransmittableThreadLocal<>();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 使用 TtlExecutors 包装
ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(executor);
context.set("Request-1");
ttlExecutor.submit(() -> {
System.out.println(context.get()); // Request-1
});
context.set("Request-2");
ttlExecutor.submit(() -> {
System.out.println(context.get()); // Request-2
});
}
}内存泄漏问题
java
public class MemoryLeakDemo {
static class LargeObject {
private byte[] data = new byte[1024 * 1024]; // 1MB
}
public static void main(String[] args) {
ThreadLocal<LargeObject> threadLocal = new ThreadLocal<>();
threadLocal.set(new LargeObject());
// 使用后必须清理
threadLocal.remove();
// 或者使用 try-finally 确保清理
try {
threadLocal.set(new LargeObject());
// 业务逻辑
} finally {
threadLocal.remove();
}
}
}
朔风