ThreadLocal使用及其原理和注意点
关键注意事项
1.必须调用 remove () 方法
线程池中的线程是复用的,如果不清除,下次复用线程时会读到旧数据,导致逻辑错误或内存泄漏。
2.避免使用 static 滥用
虽然 ThreadLocal 常声明为 static,但需明确其作用域,避免存储过大对象。
3.为什么要使用 static 修饰
如果 ThreadLocal
是非静态的(实例变量),那么每个类实例都会创建一个独立的ThreadLocal
对象。这会导致:
同一个线程中,通过不同的类实例访问 ThreadLocal
时,拿到的是不同容器中的数据(不符合 "线程内全局共享" 的预期)。
例如:UserContext
类的两个实例 ctx1
和 ctx2
,它们的非静态 ThreadLocal
会让线程 A 在 ctx1
存的数据,在 ctx2
中读不到。
4.父子线程数据不共享
子线程无法读取父线程的 ThreadLocal 数据,如需共享可使用 InheritableThreadLocal
。
注意:可以创建多份 ThreadLocal
一个线程中可能同时需要存储:
- 用户登录信息(
User
类型) - 数据库事务 ID(
String
类型) - 本次请求的日志追踪 ID(
Long
类型)
此时需要定义多个 ThreadLocal
:
public class ThreadContext {// 存储用户信息private static ThreadLocal<User> userLocal = new ThreadLocal<>();// 存储事务IDprivate static ThreadLocal<String> transactionIdLocal = new ThreadLocal<>();// 存储日志追踪IDprivate static ThreadLocal<Long> traceIdLocal = new ThreadLocal<>();// 用户信息的get/set/removepublic static void setUser(User user) {userLocal.set(user);}public static User getUser() {return userLocal.get();}public static void removeUser() {userLocal.remove();}// 事务ID的get/set/removepublic static void setTransactionId(String id) {transactionIdLocal.set(id);}public static String getTransactionId() {return transactionIdLocal.get();}public static void removeTransactionId() {transactionIdLocal.remove();}// 日志追踪ID的get/set/removepublic static Long getTraceId() {return traceIdLocal.get();}public static void setTraceId(Long id) {traceIdLocal.set(id);}public static void removeTraceId() {traceIdLocal.remove();}
}
使用上:
// 存储数据
ThreadContext.setUser(new User("张三"));
ThreadContext.setTransactionId("tx-123456");
ThreadContext.setTraceId(10086L);// 读取数据(同一线程内)
User user = ThreadContext.getUser(); // 张三
String txId = ThreadContext.getTransactionId(); // tx-123456
总结
- 静态声明 ThreadLocal:是为了保证容器实例唯一,避免资源浪费,确保线程内变量的全局一致性(最常见的使用方式)。
- 多个 ThreadLocal 实例:完全合理且必要,用于隔离同一线程中的不同类型变量(如用户信息、事务 ID 等)。
核心原则:一个 ThreadLocal 实例对应一种类型的线程变量,静态声明是为了让这种对应关系全局唯一。