ThreadLocal 快速入门
ThreadLocal
是 Java 中的一个类,用于创建线程局部变量。线程局部变量是一种特殊的变量,每个线程都有自己的副本,互相之间不会相互影响。这在多线程环境中非常有用,可以避免线程间共享变量导致的并发问题。
- 定义与作用:
ThreadLocal
是 Java 中的一个类,用于创建线程局部变量。它提供了一种在每个线程中都创建独立副本的机制。这意味着每个线程都可以访问自己的变量副本,而不会影响其他线程。 - 使用场景:
- 线程安全性:在多线程环境中,使用
ThreadLocal
可以避免共享变量的竞争条件,提高程序的线程安全性。 - 上下文传递:有些情况下,需要在同一个线程的不同方法之间传递数据,但又不希望使用方法参数或全局变量。这时候可以使用
ThreadLocal
来存储线程相关的数据,方便在整个线程的执行过程中访问。例如:项目中想要获取登录人的id,就可以使用ThreadLocal
- 线程封闭:有些对象是线程不安全的,但是如果每个线程都有自己的副本,就可以避免共享对象导致的线程安全问题。
- 线程安全性:在多线程环境中,使用
- 使用方式:
- 创建
ThreadLocal
对象:可以直接实例化ThreadLocal
类,也可以通过ThreadLocal
的静态方法withInitial(Supplier<? extends S> supplier)
来创建并初始化ThreadLocal
实例。 - 设置值:通过
set(T value)
方法设置当前线程的变量副本的值。 - 获取值:通过
get()
方法获取当前线程的变量副本的值。 - 移除值:通过
remove()
方法移除当前线程的变量副本。
- 创建
- 内存泄漏问题: 使用
ThreadLocal
时需要注意内存泄漏的问题。因为ThreadLocal
是与线程绑定的,如果线程不结束,ThreadLocal
中的值可能无法释放,导致内存泄漏。为了避免这个问题,最好在使用完ThreadLocal
后调用remove()
方法将其值移除。 - 线程池中的使用: 在使用线程池的情况下,需要特别小心
ThreadLocal
的使用。因为线程池中的线程是可以被复用的,如果ThreadLocal
的值没有在使用完后及时清理,可能会影响到其他任务的执行。通常建议在使用完ThreadLocal
后手动调用remove()
方法清理。
ThreadLocal常用方法:
A. public void set(T value) : 设置当前线程的线程局部变量的值
B. public T get() : 返回当前线程所对应的线程局部变量的值
C. public void remove() : 删除当前线程所对应的线程局部变量的值
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。 如果在后续的操作中, 我们需要在Controller / Service中要使用当前登录用户的ID, 可以直接从ThreadLocal直接获取。
案例:
项目中想要获取登录人的id
1).BaseContext工具类
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
2).LoginCheckFilter中存放当前登录用户到ThreadLocal
在doFilter方法中, 判定用户是否登录, 如果用户登录, 在放行之前, 获取HttpSession中的登录用户信息, 调用BaseContext的setCurrentId方法将当前登录用户ID存入ThreadLocal。
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
3). MyMetaObjectHandler中从ThreadLocal中获取
将之前在代码中固定的当前登录用户1, 修改为动态调用BaseContext中的getCurrentId方法获取当前登录用户ID