11 - ThreadLocal(JUC系列)

目录

入门

一、 什么是ThreadLocal?

二、代码演示

三、ThreadLocal使用注意事项

四、内存泄漏


入门

一、 什么是ThreadLocal?

ThreadLocal顾名思义,也叫做线程局部变量

每个 Thread 线程内部都有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。

既然每个 Thread 线程有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

ThreadLocal是一种可以为每个线程提供变量副本的类,使得每个线程在某一时间访问到的并不是同一个对象。这样可以隔离多个线程对数据的共享,减少了线程同步所带来的性能消耗,也减少了线程并发控制的复杂度。

二、代码演示

使用场景:比如存放用户信息


import com.mszlu.blog.dao.pojo.SysUser;

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();
    }
}
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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. 如果认证成功 放行即可
         */
        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 {
        UserThreadLocal.remove();
    }
}
package com.mszlu.blog.controller;

import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("test")
public class TestController {

    @RequestMapping
    public Result test(){
//        SysUser
        SysUser sysUser = UserThreadLocal.get();
        System.out.println(sysUser);
        return Result.success(null);
    }
}

三、ThreadLocal使用注意事项

  1. 使用ThreadLocal为避免空指针异常,则需要先初始化。因为它初始化时会先调用get()方法获取值,如果没有则会返回null值。
  2. 建议将ThreadLocal定义为static的,减少空间的浪费。
  3. 使用完一定记得remove()清除,避免内存泄漏

四、内存泄漏

每一个Thread线程内都各自维护着一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

 所谓弱引用,其实就是我们的Java对象分为强、软、弱、虚四种引用。

强引用

比如我们通常new的这些对象,都是强引用对象。

它们都具有一个特点:当触发垃圾回收时,垃圾收集器会根据根可达算法判断当前对象是否存活,如果存活就不会进行垃圾回收。

也就是说只要当前对象一直存活就不会进行垃圾回收,即使出现OOM也不会。

软引用

软引用是说对象在内存满之前,都不会进行回收。内存满了即使它存活,也会被清除掉。

弱引用

只要一触发垃圾回收,不管当前对象是否存活,都会被回收掉。

那为什么TheadLocal内部TheadLocalMap的Entry对象的key是弱引用的ThreadLoal,还会出现内存泄漏呢?

当一个线程的生命周期进行到一段时间后,我们的key被回收掉了。(可能因为新生代满了等原因,垃圾回收触发了)

这时候,key没有了,但是value还存在,因为value是强引用的。

显然,key都没有了,肯定是获取不到这个value的。

当我们线程结束时,ThreadLocalMap也不会存活了,会伴随着这个ThreadLocalMap的回收。

但是这个map找不到了,所以这块内存就回收不了。

value就一致存在内存中了。

如果有大量的这种行为,那么就很可能造成内存泄露。

所以我们用完要手动删除掉这个ThreaLocal中的对象信息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何苏三月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值