ThreadLocal 是线程专属的本地变量,它的设计是给每个线程创建一份独立的变量副本,线程之间完全不共享数据,天然实现线程隔离,全程无锁竞争,性能远高于加锁保证线程安全的方案。
每个 Thread 内部都有一个专属的成员变量 ThreadLocalMap(不是 ThreadLocal 的!),Key 是 ThreadLocal 实例本身(弱引用),Value 是存的变量(强引用)。
官话太多,直接上例子。
以用户上下文传递举个例子
/** * 用户上下文工具类 * 用ThreadLocal存当前登录用户的ID,避免层层传参 */publicclassUserContextHolder {// 定义ThreadLocal,存用户IDprivatestaticfinal ThreadLocal<Long> USER_ID_THREAD_LOCAL = newThreadLocal<>();/** * 设置用户ID */publicstaticvoidsetUserId(Long userId) { USER_ID_THREAD_LOCAL.set(userId); }/** * 获取用户ID */publicstatic Long getUserId() {return USER_ID_THREAD_LOCAL.get(); }/** * 必须手动调用!清理ThreadLocal,防止内存泄漏 */publicstaticvoidclear() { USER_ID_THREAD_LOCAL.remove(); }}和加锁保证线程安全的区别
小贴士
很多人以为ThreadLocalMap的Key是“线程ID”,其实Key是ThreadLocal实例本身,不是线程ID。
一个线程可以有多个ThreadLocal实例,每个实例对应一个Value,存到同一个ThreadLocalMap里,Key不同,互不干扰。
ThreadLocal内存泄漏问题
Java中的4种引用强度
1. 强引用
我们平时写的 Object obj = new Object() 就是强引用,只要强引用还在,GC永远不会回收这个对象。
2. 软引用
用 SoftReference 包装的引用,只有当内存不够用的时候,GC 才会回收这个对象。
3. 弱引用
用 WeakReference 包装的引用,只要GC一运行,不管内存够不够,都会回收这个对象。
4. 虚引用
用 PhantomReference 包装的引用,完全不影响对象的生命周期,只是用来在对象被 GC 回收前收到一个通知,做一些收尾工作。
ThreadLocalMap的Entry结构
ThreadLocalMap的Entry是继承自WeakReference的,结构如下:
staticclassEntryextendsWeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) {super(k); // Key是弱引用 value = v; // Value是强引用 }}假设创建了一个 ThreadLocal 实例,有外部强引用指向它,同时把它作为 Key 存到了线程的 ThreadLocalMap 里;
当用完 ThreadLocal 后,没有手动 remove,而且外部的强引用也没了(比如 threadLocal = null),因为 Key 是弱引用,GC 一运行,Key 就被回收了,变成了 null。
但是, Value 是强引用,只要线程还活着,Value 就不会被 GC 回收,这就导致了内存泄漏。
更严重的还有,如果用了线程池,线程会被长期复用,不仅内存泄漏,还会导致用户信息串号、数据错误等严重的业务问题。
怎么避免内存泄漏
使用完ThreadLocal后,手动调用 remove() 方法。
举个例子,在Spring Boot的拦截器里:
@ComponentpublicclassUserInterceptorimplementsHandlerInterceptor {@OverridepublicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 从Token里解析出用户ID,存到ThreadLocalLonguserId= parseUserIdFromToken(request.getHeader("Authorization")); UserContextHolder.setUserId(userId);returntrue; }@OverridepublicvoidafterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 在这里清理,不管请求成功失败,都要清理。 UserContextHolder.clear(); }}