threadlocal原理以及内存泄露问题

原理:ThreadLocal是线程本地变量,每个线程存在副本,有一个ThreadLocal变量,有线程1,线程2,线程3,threadlocal对三个线程进行判断,set线程1,2,3,threadlocal的引用和value,它的实现原理是每一个线程都有一个threadlocalmap,这个map里面都一个key和value,key就是thread,value就是刚开始调用threadlocal里面set进去的值,达到了线程副本的作用,线程隔离,每个线程之间互不干扰。

 threadlocal的内存泄露:比如在栈内存中有一个变量threadlocal和threadlocalmap,栈内存中的threadlocal就会去引用堆内存中的threadlocal对象,那么栈内存中threadlocal跟堆内存中threadlocal就是强引用(只要强引用存在,垃圾回收器就不会回收被引用的对象)的关系,对与单个线程threadlocalmap来说同样也会引用堆内存的某一块具体的地址,根据map的特点,map的key也会引用threadlocal,假如栈内存中threadlocal变量被置为null,但是map依然存在引用,导致了内存的泄露,虽然threadlocal本身已经做了一个优化,把key的强引用转化成了弱引用,但是同时value也存在强引用的关系,value并不会被释放,因此依然存在内存的泄露的问题,所以推荐调用threadlocalmap的remove方法去移除变量,释放内存。


下面是我在项目中代码的体现:

主要是用threadlocal代替session保存用户信息,这样子的好处是:可以在同一个线程中很方便的获取用户信息,不用频繁的去传递session对象,因为是在同一个主线程thread里面,所以不用担心取错对象,是同一个threadlocalmap里面。

在执行登录logincontroller之前,有一个拦截器preHandler 1、需要判断请求的接口路径是否为HandlerMethod(controller方法); 2、判断token是否为空,如果为空则未登录;3、如果token 不为空;登录验证(loginService checkToken); 4、如果认证成功就就放行。

认证成功之后会有一个token,此时token存放在redis里面,redis里面存放了用户信息,将用户信息存放到threadlocal中,在本次请求中持有用户信息,既可在后续操作中使用到用户信息。

/**
 * @ClassName UserThreadLocal
 * @Description TODO
 * @Author zl
 * @Date 2022/4/2 15:13
 */
public class UserThreadLocal {

    private UserThreadLocal(){}
    //线程变量隔离
    private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();

    public static void put(SysUser sysUser){
        LOCAL.set(sysUser);
    }

    public static SysUser get(){
        return LOCAL.get();
    }

    public static void remove(){
        LOCAL.remove();
    }
}
/**
 * @ClassName LoginInterceptor
 * @Description TODO
 * @Author zl
 * @Date 2022/4/2 13:21
 */
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private LoginService loginService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //在执行controller方法(handler)之前进行执行
        /**
         * 1、需要判断请求的接口路径是否为HandlerMethod(controller方法)
         * 2、判断token是否为空,如果为空则未登录
         * 3、如果token 不为空;登录验证(loginService checkToken)
         * 4、如果认证成功就就放行
         * */
        //判断请求的类型  如果不是访问的controller就放行
        if (!(handler instanceof HandlerMethod)){
            //handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询
            return true;
        }
        String token = request.getHeader("Authorization");

        log.info("=================request start===========================");
        String requestURI = request.getRequestURI();
        log.info("request uri:{}",requestURI);
        log.info("request method:{}",request.getMethod());
        log.info("token:{}", token);
        log.info("=================request end===========================");


        if (StringUtils.isBlank(token)){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        SysUser sysUser = loginService.checkToken(token);
        if (sysUser == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        //登录验证成功,放行
        //我希望在controller中 直接获取用户的信息 怎么获取?
        UserThreadLocal.put(sysUser);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险
        UserThreadLocal.remove();
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值