外賣等實(shí)踐項目問題記錄(1)
ThreadLocal內(nèi)存泄漏問題
最近在復(fù)盤重新黑馬還有尚硅谷一些項目,發(fā)現(xiàn)我們在使用獲取當(dāng)前用戶id這項功能的時候主要是使用ThreadLocal來進(jìn)行存取
定義:
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 { //判斷當(dāng)前攔截到的是Controller的方法還是其他資源 if (!(handler instanceof HandlerMethod)) { //當(dāng)前攔截到的不是動態(tài)方法,直接放行 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("當(dāng)前用戶的id:", userId); BaseContext.setCurrentId(userId); //3、通過,放行 return true; } catch (Exception ex) { //4、不通過,響應(yīng)401狀態(tài)碼 response.setStatus(401); return false; } } }
獲?。?/strong>
//清空當(dāng)前用戶的購物車數(shù)據(jù) shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());
但我們知道ThreadLocal是存在內(nèi)存泄漏問題的
原因分析
ThreadLocal的工作原理 ThreadLocal通過為每個線程維護(hù)一個獨(dú)立的變量副本,實(shí)現(xiàn)了線程間的數(shù)據(jù)隔離。具體來說,ThreadLocal的值存儲在Thread對象的ThreadLocalMap中,鍵是ThreadLocal實(shí)例,值是存儲的數(shù)據(jù)。
內(nèi)存泄漏的可能性
如果ThreadLocal變量沒有被正確清理(調(diào)用remove()方法),即使ThreadLocal實(shí)例本身被回收,ThreadLocalMap中的鍵(弱引用)可能已經(jīng)被回收,但值仍然存在,導(dǎo)致無法釋放內(nèi)存。這種情況尤其容易發(fā)生在使用線程池的場景中,因為線程池中的線程是復(fù)用的,線程的生命周期很長。
我們整個項目里面只用到了存取ThreadLocal中的線程id,卻并沒有在方法結(jié)束后對ThreadLocal進(jìn)行remove這樣會造成一定的風(fēng)險。
如果線程池中的線程被復(fù)用,而ThreadLocal沒有及時清理,可能會導(dǎo)致數(shù)據(jù)污染。例如,前一個請求的用戶ID殘留到了下一個請求中。
解決方案
在本項目中,我們可以修改原本的攔截器,在請求結(jié)束時統(tǒng)一清理ThreadLocal中的數(shù)據(jù)。例如:
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("請求結(jié)束,執(zhí)行afterCompletion方法..."); // 在這里編寫請求結(jié)束后的邏輯,例如清理ThreadLocal數(shù)據(jù) BaseContext.removeCurrentId(); // 清理當(dāng)前用戶ID }
使用afterCompletion方法
afterCompletion的作用 afterCompletion方法在請求處理完成(包括視圖渲染)后執(zhí)行,無論請求是否成功或拋出異常。它適合用于清理資源、記錄日志等操作。
不知道我這樣做的是否正確,歡迎各位大佬指教!
#實(shí)習(xí)##蒼穹外賣項目包裝#