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 userLocal = new ThreadLocal(); // 存储事务ID private static ThreadLocal transactionIdLocal = new ThreadLocal(); // 存储日志追踪ID private static ThreadLocal traceIdLocal = new ThreadLocal(); // 用户信息的get/set/remove public static void setUser(User user) { userLocal.set(user); } public static User getUser() { return userLocal.get(); } public static void removeUser() { userLocal.remove(); } // 事务ID的get/set/remove public static void setTransactionId(String id) { transactionIdLocal.set(id); } public static String getTransactionId() { return transactionIdLocal.get(); } public static void removeTransactionId() { transactionIdLocal.remove(); } // 日志追踪ID的get/set/remove public 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 实例对应一种类型的线程变量,静态声明是为了让这种对应关系全局唯一。