• 在线博客系统——ThreadLocal详解


    目录

    项目分析

    编码实现

    前端测试

    ThreadLocal

    ThreadLocal特点

    ThreadLocal 和Synchronized

    ThreadLocal内部结构

    ThreadLocal内存泄漏


    项目分析

    在本项目中:

    redis中可以获取用户信息,但是因为redis中的key是token,要先拿到token才能拿到用户信息,但是token不是每个类中都存在

    ThreadLocal说简单点就是往一个线程存数据,这个数据只能由这个线程获取,其他线程无法获取这个数据
     

    ThreadLocal内存泄漏:

    ThreadLocal的键是弱引用,值是强引用,键被清理了,值还在但是拿不到

    四种引用类型:

                    强引用(不会被回收):最普通的引用 Object o = new Object()

                    软引用(内存不足时回收): 垃圾回收器, 内存不够的时候回收 (缓存)

                    弱引用(gc时直接回收):垃圾回收器看见就会回收 (防止内存泄漏)

                    虚引用(差不多不存在的引用):垃圾回收器看见就回收(管理堆外内存) 

    所以,每一次请求都会进行存储,又因为是强引用,如果不删除,就会出现内存泄露的风险

    我们必须手动删除这个节点,线程结束必然伴随ThreadLocalMap的回收,

    Map没了这块内存就永远收不回了,内存泄漏

    编码实现

    UserThreadLocal:

    1. package com.huing.blog.utils;
    2. import com.huing.blog.dao.pojo.SysUser;
    3. /**
    4. * @Author huing
    5. * @Create 2022-07-06 10:26
    6. */
    7. public class UserThreadLocal {
    8. private UserThreadLocal(){}
    9. private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
    10. public static void put(SysUser user){
    11. LOCAL.set(user);
    12. }
    13. public static SysUser get(){
    14. return LOCAL.get();
    15. }
    16. public static void remove(){
    17. LOCAL.remove();
    18. }
    19. }

    LoginInterceptor:

    1. package com.huing.blog.handler;
    2. import com.alibaba.fastjson.JSON;
    3. import com.huing.blog.dao.pojo.SysUser;
    4. import com.huing.blog.service.LoginService;
    5. import com.huing.blog.utils.UserThreadLocal;
    6. import com.huing.blog.vo.ErrorCode;
    7. import com.huing.blog.vo.Result;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.stereotype.Component;
    11. import org.springframework.web.method.HandlerMethod;
    12. import org.springframework.web.servlet.HandlerInterceptor;
    13. import javax.servlet.http.HttpServletRequest;
    14. import javax.servlet.http.HttpServletResponse;
    15. /**
    16. * @Author huing
    17. * @Create 2022-07-05 18:10
    18. */
    19. @Component
    20. @Slf4j
    21. public class LoginInterceptor implements HandlerInterceptor {
    22. @Autowired
    23. private LoginService loginService;
    24. @Override
    25. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    26. //在执行controller方法(在springMVC中叫做handler)之前进行执行
    27. /**
    28. * 1.需要判断请求的接口路径是否为HandlerMethod(Controller方法)
    29. * 2.判断token是否为空 如果为空 未登录
    30. * 3.如果token不为空,登录验证 loginService checkToken
    31. * 4.如果认证成功,放行即可
    32. */
    33. //如果不是我们的方法进行放行
    34. if (!(handler instanceof HandlerMethod)) {
    35. //handler 可能是访问资源的RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询
    36. return false;
    37. }
    38. String token = request.getHeader("Authorization");
    39. log.info("=================request start===========================");
    40. String requestURI = request.getRequestURI();
    41. log.info("request uri:{}",requestURI);
    42. log.info("request method:{}",request.getMethod());
    43. log.info("token:{}", token);
    44. log.info("=================request end===========================");
    45. if (token == null){
    46. Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
    47. //设置浏览器识别返回的是json
    48. response.setContentType("application/json;charset=utf-8");
    49. //https://www.cnblogs.com/qlqwjy/p/7455706.html response.getWriter().print()
    50. //SON.toJSONString则是将对象转化为Json字符串
    51. response.getWriter().print(JSON.toJSONString(result));
    52. return false;
    53. }
    54. SysUser sysUser = loginService.checkToken(token);
    55. if (sysUser == null){
    56. Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
    57. //设置浏览器识别返回的是json
    58. response.setContentType("application/json;charset=utf-8");
    59. //https://www.cnblogs.com/qlqwjy/p/7455706.html response.getWriter().print()
    60. //SON.toJSONString则是将对象转化为Json字符串
    61. response.getWriter().print(JSON.toJSONString(result));
    62. return false;
    63. }
    64. //登陆成功放行
    65. //我希望在controller中直接获取用户的信息怎么获取
    66. UserThreadLocal.put(sysUser);
    67. return true;
    68. }
    69. @Override
    70. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    71. //如果不删 ThreadLocal中用完的信息会有内存泄漏的风险
    72. UserThreadLocal.remove();
    73. }
    74. }

    前端测试

    编写测试Controller

    1. package com.huing.blog.controller;
    2. import com.huing.blog.dao.pojo.SysUser;
    3. import com.huing.blog.utils.UserThreadLocal;
    4. import com.huing.blog.vo.Result;
    5. import org.springframework.web.bind.annotation.RequestMapping;
    6. import org.springframework.web.bind.annotation.RestController;
    7. /**
    8. * @author huing
    9. * @create 2022-06-24 13:34
    10. */
    11. @RestController
    12. @RequestMapping("test")
    13. public class TestController {
    14. @RequestMapping
    15. public Result test(){
    16. SysUser sysUser = UserThreadLocal.get();
    17. System.err.println(sysUser);
    18. return Result.success(null);
    19. }
    20. }

     

     

    ThreadLocal

    Java官方文档中描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变星=相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。

    ThreadLocal说简单点就是往一个线程存数据,这个数据只能由这个线程获取,其他线程无法获取这个数据

    ThreadLocal特点

    线程并发:在多线程并发场景下使用

    传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量(保存每个线程的数据,在需                    要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题)

    线程隔离:每个线程的变量都是独立的, 不会互相影响

    ThreadLocal 和Synchronized

    ThreadLocal模式与Synchronized关键字都用于处理多线程并发访问变量的问题

    ThreadLocal:以空间换取时间的思想, 为每一个线程都提供了一份变量的副本, 从而实现同访问而                          相不干扰。

                            多线程中让每个线程之间的数据相互隔离

    Synchronized:以时间换取空间的思想,只提供了一份变量, 让不同的线程排队访问。

                             多个线程之间访问资源的同步

    ThreadLocal内部结构

    1. 每个THreadLocal线程内部都有一个Map(ThreadLocalMap)
    2.  Map里面存储的ThreadLocal对象(作为key)和线程变量副本(Value)也就是存储的值
    3. Thread内部的Map是由ThreadLocal维护的, 有ThreadLocal负责向map获取和设置线程变量值
    4. 对于不同的线程, 每次获取value(也就是副本值),别的线程并不能获取当前线程的副本值, 形成了副本的隔离,互不干扰。

    ThreadLocal内存泄漏

    内存泄漏:

    • 内存溢出: Memory overflow 没有足够的内存提供申请者使用.
    • 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因,程序未释放或者无法释放,,造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果。内存泄漏的堆积终将导致内存溢出。

    由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。

  • 相关阅读:
    LeetCode(15)分发糖果【数组/字符串】【困难】
    【JAVA-C】流程控制 for 编程题
    028-GUI事件处理,ActionListener事件,MouseListener事件
    m基于基站休眠的LTE-A异构网络中节能算法matlab仿真
    财经新闻查询易语言代码
    (C++)字符串相加
    进程死锁的定义,必要条件,发生时机以及相关处理策略
    Spring源码阅读-ClassPathXmlApplicationContext
    【RabbitMQ】——延迟队列
    算法 杨辉三角求解 java打印杨辉三角 多路递归打印杨辉三角 递归优化杨辉三角 记忆法优化递归 帕斯卡三角形 算法(十二)
  • 原文地址:https://blog.csdn.net/qq_56851614/article/details/125634374