当前位置: 首页 > article >正文

彻底理解ThreadLocal的应用场景和底层实现

一.概念

定义:

ThreadLocal 是 Java 中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据。

其实是可以通过调用 Set() 方法往里面存入值,存入的值是每个线程互相隔离、互不影响的,每个线程都有一个 ThreadLocal 的空间来存 ThreadLocal 的数据,那么它是怎么做到每个线程互相隔离、互不影响的呢?这就需要从底层数据结构分析了。

二.底层数据结构

先看下图:

1.在线程上,有一个叫ThreadLocalMap的变量,这个变量用来存储当前线程的ThreadLocal的数据,ThreadLocalMap里面可能有多个entry对象,每个entry都由一个Key-Value组成;

2.当调用threadLocal.set()方法的时候,其实就是先拿到当前线程中的ThreadLocalMap,拿到以后再将这个threadLocal放到entry的key上面,再将这个存储的值放在value里面;

3.在当前线程中使用了多个threadLocal时,会存储多个entry,并且这些entry都会绑定当前线程。

通过上诉的流程,就达到了每个线程的相互隔离、互不影响。

接下来再通过源码进行查看:

    ThreadLocal.ThreadLocalMap threadLocals = null;

    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 不为空就直接插入当前ThreadLocalMap中
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

其实可以理解ThreadLocal其实是用来操作当前线程中的ThreadLocalMap的一个工具类,通过上诉的set()方法,就可以实现每个线程的相互隔离、互不影响。

总结:

ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值

三.使用注意事项

如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把Entry对象(设置的key-value)进行回收,但线程池中的核心线程是不会被回收的,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清除Entry对象。

四.应用场景 

ThreadLocal的使用场景很多,但是它都遵守一个原则:当一个共享变量是共享的,但是需要每个线程互不影响,相互隔离,就可以使用ThreadLocal

日常开发常用的两种场景:

1.跨层传递信息的时候,每个方法都声明一个参数很麻烦, A\B\C\D 4个类互相传递每个方法都声明参数降低了维护性,可以用一个ThreadLocal共享变量,在A存值,BCD都可以获取;(比如有一个User需要在ABCD类中传递,那么4个类都需要定义User,并且当User的参数修改后,4个类都需要修改,这也不方便进行维护,所以可以用一个ThreadLocal共享变量来实现)

2.隔离线程,存储一些线程不安全的工具对象(如SimpleDateFormat,在多线程的场景下去做日期的格式化,就可能出现线程不安全的问题);

框架中的使用:

1.Spring中的事务管理器就是使用的ThreadLocal;(在多线程情况下,声明式事务是没办法达到一致性的,因为一个线程就存储了一个事务)

2.Springmvc的HttpSession、HttpServletReugest、HttpServletResponse都是放在ThreadLocal,因为servlet是单例的,而springmvc允许在controller类中通过@Autowired配置request、response以及requestcontext等实例对象,底层就是搭配Threadlocal才实现线程安全。(request、response如果是单列的,就会出现线程安全问题,因为每个请求都有自己的request和response,所以它们的底层都是通过ThreadLocal也进行存储的这些对象的)


http://www.kler.cn/a/428087.html

相关文章:

  • C++中常用的十大排序方法之1——冒泡排序
  • Docker/K8S
  • 安卓逆向之脱壳-认识一下动态加载 双亲委派(二)
  • Java面试题2025-并发编程基础(多线程、锁、阻塞队列)
  • Linux 4.19内核中的内存管理:x86_64架构下的实现与源码解析
  • olloama下载deepseek-r1大模型本地部署
  • C++多态性
  • 项目页面渲染学习总结
  • 【JavaWeb后端学习笔记】Spring全局异常处理器
  • 【论文笔记】Compact Language Models via Pruning and Knowledge Distillation
  • R155 VTA 认证对汽车入侵检测系统(IDS)合规要求
  • World of Warcraft (version update)
  • 蓝牙键鼠无法被电脑识别
  • 雨晨 2610(2)0.2510 Windows 11 24H2 Iot 企业版 LTSC 2024 极简 2in1
  • NOIP2011 普及组【瑞士轮】题解(AC)
  • vue2+html2canvas+js PDF实现试卷导出和打印功能
  • 解决Ubuntu在VMware关机时,老是一个光标在那里闪动几分钟,才能关机的问题
  • Docker 学习总结(84)—— Docker 常用运维命令
  • axios的get和post请求,关于携带参数相关的讲解一下
  • [0629].第29节:配置中心业务规则与动态刷新
  • 运维角度定位JAVA微服务线上CPU飙升问题
  • iOS如何操作更新推送证书
  • 基于单片机的智能客车超载监测综合性实验
  • Ubuntu 环境美化
  • 人机之间的交互存在不少逻辑性的跳跃
  • Hadoop3集群实战:从零开始的搭建之旅