一、什么是 ThreadLocal?
- ThreadLocal(线程局部变量)是 Java 提供的一种机制,它能让每个线程拥有自己独立的变量副本。
- 换句话说,多个线程访问同一个 ThreadLocal 变量时,彼此之间不会干扰,每个线程都能独立存取自己的值。
- 这解决了多线程共享变量时的线程安全问题。
举个简单例子:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
new Thread(() -> {
threadLocal.set(100);
System.out.println("线程1的值:" + threadLocal.get()); // 输出100
}).start();
new Thread(() -> {
threadLocal.set(200);
System.out.println("线程2的值:" + threadLocal.get()); // 输出200
}).start();
两个线程分别设置自己的值,互不影响。
二、InheritableThreadLocal 是什么?
- InheritableThreadLocal 是 ThreadLocal 的子类,特点是:子线程可以继承父线程的值。
- 这在父线程创建子线程时,子线程自动获得父线程的变量副本。
代码示例:
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("主线程的值");
new Thread(() -> {
System.out.println("子线程继承的值:" + inheritableThreadLocal.get());
}).start();
}
输出:
子线程继承的值:主线程的值
三、为什么用 ThreadLocal 存储用户登录信息?
- 在 Web 应用中,每个请求由不同线程处理。
- 我们希望在请求处理过程中,能方便地获取当前登录用户信息。
- 使用 ThreadLocal 可以让每个线程保存自己的用户信息,避免多线程间数据混乱。
- 这样,在同一个线程的不同方法调用中,都能通过 ThreadLocal 轻松获取用户信息。
四、实战:用 ThreadLocal 存储用户登录信息
1. 定义用户信息类 UserRuntime
public class UserRuntime {
private String userId;
private String userName;
// 其他用户相关字段...
public UserRuntime(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
public String getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
}
2. 定义 ThreadLocal 容器类 UserRuntimeHolder
public class UserRuntimeHolder {
// 使用 InheritableThreadLocal,方便子线程继承父线程用户信息
private static final ThreadLocal<UserRuntime> CURRENT = new InheritableThreadLocal<>();
public static void set(UserRuntime userRuntime) {
CURRENT.set(userRuntime);
}
public static UserRuntime get() {
return CURRENT.get();
}
public static void remove() {
CURRENT.remove();
}
}
3. 自定义过滤器,拦截请求,设置用户信息
假设你用的是 Spring Boot,可以这样写:
@Component
public class UserRuntimeFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// 模拟从请求中获取用户ID和用户名(实际项目中从token或session获取)
String userId = request.getParameter("userId");
String userName = request.getParameter("userName");
if (userId != null && userName != null) {
UserRuntime userRuntime = new UserRuntime(userId, userName);
UserRuntimeHolder.set(userRuntime);
}
chain.doFilter(request, response);
} finally {
// 请求结束,清理线程变量,防止内存泄漏
UserRuntimeHolder.remove();
}
}
}
4. 在业务代码中获取当前用户信息
public class UserService {
public void printCurrentUser() {
UserRuntime user = UserRuntimeHolder.get();
if (user != null) {
System.out.println("当前用户ID:" + user.getUserId());
System.out.println("当前用户名:" + user.getUserName());
} else {
System.out.println("没有用户登录");
}
}
}
五、总结与注意事项
| 关键点 | 说明 |
|---|---|
| ThreadLocal | 每个线程拥有独立变量副本,线程间互不干扰 |
| InheritableThreadLocal | 子线程可以继承父线程的ThreadLocal值 |
| 使用场景 | 需要线程隔离但方法间共享数据,如用户登录信息存储 |
| 资源清理 | 使用完ThreadLocal后,必须调用remove(),防止内存泄漏 |
| 静态变量存储ThreadLocal | ThreadLocal实例一般声明为private static final,保证唯一性 |
六、完整示例代码汇总
// 用户信息类
public class UserRuntime {
private String userId;
private String userName;
public UserRuntime(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
public String getUserId() { return userId; }
public String getUserName() { return userName; }
}
// ThreadLocal容器
public class UserRuntimeHolder {
private static final ThreadLocal<UserRuntime> CURRENT = new InheritableThreadLocal<>();
public static void set(UserRuntime userRuntime) {
CURRENT.set(userRuntime);
}
public static UserRuntime get() {
return CURRENT.get();
}
public static void remove() {
CURRENT.remove();
}
}
// 过滤器示例(Spring Boot)
@Component
public class UserRuntimeFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
String userId = request.getParameter("userId");
String userName = request.getParameter("userName");
if (userId != null && userName != null) {
UserRuntimeHolder.set(new UserRuntime(userId, userName));
}
chain.doFilter(request, response);
} finally {
UserRuntimeHolder.remove();
}
}
}
// 业务代码示例
public class UserService {
public void printCurrentUser() {
UserRuntime user = UserRuntimeHolder.get();
if (user != null) {
System.out.println("当前用户ID:" + user.getUserId());
System.out.println("当前用户名:" + user.getUserName());
} else {
System.out.println("没有用户登录");
}
}
}
七、实用指标参考
- 线程安全:ThreadLocal 变量天然线程安全,无需额外同步。
- 性能开销:ThreadLocal 变量访问速度快,适合存储短生命周期的线程数据。
- 内存管理:务必调用
remove(),避免线程池复用时造成内存泄漏。
通过以上内容,你可以轻松理解 ThreadLocal 和 InheritableThreadLocal 的基本原理,掌握如何在 Java Web 应用中用它们存储和获取用户登录信息,实现线程安全且方便的用户信息管理。