目录
在本项目中:
redis中可以获取用户信息,但是因为redis中的key是token,要先拿到token才能拿到用户信息,但是token不是每个类中都存在
ThreadLocal说简单点就是往一个线程存数据,这个数据只能由这个线程获取,其他线程无法获取这个数据
ThreadLocal内存泄漏:
ThreadLocal的键是弱引用,值是强引用,键被清理了,值还在但是拿不到
四种引用类型:
强引用(不会被回收):最普通的引用 Object o = new Object()
软引用(内存不足时回收): 垃圾回收器, 内存不够的时候回收 (缓存)
弱引用(gc时直接回收):垃圾回收器看见就会回收 (防止内存泄漏)
虚引用(差不多不存在的引用):垃圾回收器看见就回收(管理堆外内存)
所以,每一次请求都会进行存储,又因为是强引用,如果不删除,就会出现内存泄露的风险
我们必须手动删除这个节点,线程结束必然伴随ThreadLocalMap的回收,
Map没了这块内存就永远收不回了,内存泄漏
UserThreadLocal:
- package com.huing.blog.utils;
-
- import com.huing.blog.dao.pojo.SysUser;
-
- /**
- * @Author huing
- * @Create 2022-07-06 10:26
- */
- public class UserThreadLocal {
-
- private UserThreadLocal(){}
-
- private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
-
- public static void put(SysUser user){
- LOCAL.set(user);
- }
-
- public static SysUser get(){
- return LOCAL.get();
- }
-
- public static void remove(){
- LOCAL.remove();
- }
- }
LoginInterceptor:
- package com.huing.blog.handler;
-
- import com.alibaba.fastjson.JSON;
- import com.huing.blog.dao.pojo.SysUser;
- import com.huing.blog.service.LoginService;
- import com.huing.blog.utils.UserThreadLocal;
- import com.huing.blog.vo.ErrorCode;
- import com.huing.blog.vo.Result;
- import lombok.extern.slf4j.Slf4j;
- 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;
-
- /**
- * @Author huing
- * @Create 2022-07-05 18:10
- */
- @Component
- @Slf4j
- public class LoginInterceptor implements HandlerInterceptor {
-
- @Autowired
- private LoginService loginService;
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- //在执行controller方法(在springMVC中叫做handler)之前进行执行
- /**
- * 1.需要判断请求的接口路径是否为HandlerMethod(Controller方法)
- * 2.判断token是否为空 如果为空 未登录
- * 3.如果token不为空,登录验证 loginService checkToken
- * 4.如果认证成功,放行即可
- */
- //如果不是我们的方法进行放行
- if (!(handler instanceof HandlerMethod)) {
- //handler 可能是访问资源的RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询
- return false;
- }
-
- 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 (token == null){
- Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
- //设置浏览器识别返回的是json
- response.setContentType("application/json;charset=utf-8");
- //https://www.cnblogs.com/qlqwjy/p/7455706.html response.getWriter().print()
- //SON.toJSONString则是将对象转化为Json字符串
- response.getWriter().print(JSON.toJSONString(result));
- return false;
- }
-
- SysUser sysUser = loginService.checkToken(token);
- if (sysUser == null){
- Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
- //设置浏览器识别返回的是json
- response.setContentType("application/json;charset=utf-8");
- //https://www.cnblogs.com/qlqwjy/p/7455706.html response.getWriter().print()
- //SON.toJSONString则是将对象转化为Json字符串
- 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();
- }
- }
编写测试Controller
- package com.huing.blog.controller;
-
- import com.huing.blog.dao.pojo.SysUser;
- import com.huing.blog.utils.UserThreadLocal;
- import com.huing.blog.vo.Result;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * @author huing
- * @create 2022-06-24 13:34
- */
- @RestController
- @RequestMapping("test")
- public class TestController {
-
- @RequestMapping
- public Result test(){
- SysUser sysUser = UserThreadLocal.get();
- System.err.println(sysUser);
- return Result.success(null);
- }
- }


Java官方文档中描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变星=相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。
ThreadLocal说简单点就是往一个线程存数据,这个数据只能由这个线程获取,其他线程无法获取这个数据
线程并发:在多线程并发场景下使用
传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量(保存每个线程的数据,在需 要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题)
线程隔离:每个线程的变量都是独立的, 不会互相影响
ThreadLocal模式与Synchronized关键字都用于处理多线程并发访问变量的问题
ThreadLocal:以空间换取时间的思想, 为每一个线程都提供了一份变量的副本, 从而实现同访问而 相不干扰。
多线程中让每个线程之间的数据相互隔离
Synchronized:以时间换取空间的思想,只提供了一份变量, 让不同的线程排队访问。
多个线程之间访问资源的同步
- 每个THreadLocal线程内部都有一个Map(ThreadLocalMap)
- Map里面存储的ThreadLocal对象(作为key)和线程变量副本(Value)也就是存储的值
- Thread内部的Map是由ThreadLocal维护的, 有ThreadLocal负责向map获取和设置线程变量值
- 对于不同的线程, 每次获取value(也就是副本值),别的线程并不能获取当前线程的副本值, 形成了副本的隔离,互不干扰。
内存泄漏:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。