ThreadLocal的弱引用机制在何种场景下会生效?
一、你的静态ThreadLocal场景下,弱引用的作用时机
你定义的BaseContext.threadLocal是静态强引用,只要BaseContext类未被卸载,这个ThreadLocal对象的强引用就会一直存在,ThreadLocalMap中对应的Entry的key(弱引用)不会被GC回收,看起来弱引用好像没发挥作用,但有两种情况它会生效:
- Web应用重新部署时:当Web容器(如Tomcat)重新部署应用时,旧的类加载器会被回收,
BaseContext类会被卸载,静态threadLocal的强引用随之消失。此时ThreadLocal对象会被GC,对应的ThreadLocalMap Entry的key变为null,后续线程调用set()/get()/remove()方法时,ThreadLocalMap会自动清理这些key为null的Entry,避免内存泄漏。 - 线程终止时:如果线程被销毁(而非线程池复用),整个ThreadLocalMap会随着线程对象被GC回收,弱引用机制在这里起到的辅助清理作用不明显,但如果线程未被销毁(线程池),类卸载的场景就是弱引用发挥作用的关键时机。
不过要特别注意:即使有弱引用兜底,请求结束后必须调用removeCurrentId()。如果遗漏调用(比如异常导致拦截器的finally块未执行),线程复用的时候可能会拿到之前请求的用户ID,引发业务逻辑错误;同时,value对象会一直被ThreadLocalMap持有,造成内存泄漏(尤其是value是大对象时)。
二、ThreadLocal的其他常见定义场景(弱引用发挥核心作用)
弱引用设计的核心价值,主要体现在ThreadLocal没有全局强引用的场景中:
实例级ThreadLocal:在普通类中定义非静态的ThreadLocal,比如:
public class UserService { private ThreadLocal<Long> userIdThreadLocal = new ThreadLocal<>(); public void processUser(Long userId) { userIdThreadLocal.set(userId); // 业务逻辑 userIdThreadLocal.remove(); } }当
UserService的实例被回收(外部强引用消失),userIdThreadLocal的强引用也会消失,此时ThreadLocal对象会被GC,对应的Entry的key变为null,后续ThreadLocalMap的操作会自动清理这些废弃Entry,避免内存泄漏。局部变量ThreadLocal:在方法内部定义ThreadLocal作为局部变量,比如:
public void handleRequest(HttpServletRequest request) { ThreadLocal<Long> tempThreadLocal = new ThreadLocal<>(); tempThreadLocal.set(getUserIdFromRequest(request)); // 处理逻辑 tempThreadLocal.remove(); }方法执行完毕后,局部变量
tempThreadLocal的强引用消失,ThreadLocal对象会被GC,对应的Entry会被后续操作清理,无需担心内存泄漏。动态创建的ThreadLocal:比如通过工厂方法动态生成ThreadLocal,后续不再持有强引用:
public ThreadLocal<Long> createUserIdThreadLocal() { return new ThreadLocal<>(); } // 使用时 ThreadLocal<Long> userIdTl = createUserIdThreadLocal(); userIdTl.set(123L); // 后续不再使用userIdTl,强引用消失此时ThreadLocal对象的强引用仅存在于局部变量中,一旦局部变量失效,ThreadLocal会被GC,弱引用机制会自动清理对应的Entry。
内容的提问来源于stack exchange,提问作者GP05

