外卖等实践项目问题记录(1)
ThreadLocal内存泄漏问题
最近在复盘重新黑马还有尚硅谷一些项目,发现我们在使用获取当前用户id这项功能的时候主要是使用ThreadLocal来进行存取
定义:
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
存入:
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户的id:", userId);
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
获取:
//清空当前用户的购物车数据 shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());
但我们知道ThreadLocal是存在内存泄漏问题的
原因分析
ThreadLocal的工作原理 ThreadLocal通过为每个线程维护一个独立的变量副本,实现了线程间的数据隔离。具体来说,ThreadLocal的值存储在Thread对象的ThreadLocalMap中,键是ThreadLocal实例,值是存储的数据。
内存泄漏的可能性
如果ThreadLocal变量没有被正确清理(调用remove()方法),即使ThreadLocal实例本身被回收,ThreadLocalMap中的键(弱引用)可能已经被回收,但值仍然存在,导致无法释放内存。这种情况尤其容易发生在使用线程池的场景中,因为线程池中的线程是复用的,线程的生命周期很长。
我们整个项目里面只用到了存取ThreadLocal中的线程id,却并没有在方法结束后对ThreadLocal进行remove这样会造成一定的风险。
如果线程池中的线程被复用,而ThreadLocal没有及时清理,可能会导致数据污染。例如,前一个请求的用户ID残留到了下一个请求中。
解决方案
在本项目中,我们可以修改原本的拦截器,在请求结束时统一清理ThreadLocal中的数据。例如:
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("请求结束,执行afterCompletion方法...");
// 在这里编写请求结束后的逻辑,例如清理ThreadLocal数据
BaseContext.removeCurrentId(); // 清理当前用户ID
}
使用afterCompletion方法
afterCompletion的作用 afterCompletion方法在请求处理完成(包括视图渲染)后执行,无论请求是否成功或抛出异常。它适合用于清理资源、记录日志等操作。
不知道我这样做的是否正确,欢迎各位大佬指教!
#实习##苍穹外卖项目包装#

查看7道真题和解析