外卖等实践项目问题记录(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方法在请求处理完成(包括视图渲染)后执行,无论请求是否成功或抛出异常。它适合用于清理资源、记录日志等操作。

不知道我这样做的是否正确,欢迎各位大佬指教!

#实习##苍穹外卖项目包装#
全部评论

相关推荐

评论
1
4
分享

创作者周榜

更多
牛客网
牛客企业服务