理解 ThreadLocal 存储用户登录信息及实战案例

478 阅读3分钟

一、什么是 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(),防止内存泄漏
静态变量存储ThreadLocalThreadLocal实例一般声明为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 应用中用它们存储和获取用户登录信息,实现线程安全且方便的用户信息管理。