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

TreadLocal和TreadLocalMap

文章目录

  • 一、ThreadLocal
  • 二、ThreadLocal的线程隔离
    • 1.set方法源码
    • 2.get方法源码
  • 三、ThreadLocalMap
    • 1.ThreadLocalMap的哈希冲突
    • 2.过期Key的探测式清理
  • 四、ThreadLocalg内存泄漏问题
  • 五、InheritableThreadLocal:父子线程数据传递
  • 实际应用场景
    • 1.数据库连接管理
    • 2.用户会话信息存储
  • 面试问题

一、ThreadLocal

在多线程环境下,多个线程访问同一个共享变量时,容易出现线程安全问题。我们通常用synchronized或Lock来加锁解决,但加锁会让线程排队执行,降低并发效率。

ThreadLocal提供了另一种思路:为每个线程创建独立的变量副本,每个线程操作自己的那份副本,互不干扰。这样既避免了线程安全问题,又无需加锁,性能更高。

  • set(T value):在当前线程中设置一个值。
  • get():获取当前线程中存储的值。
  • remove():移除当前线程中存储的值,防止内存泄漏(非常重要) 。
classThreadLocalDemo{// 声明一个ThreadLocal变量(建议用static final修饰)privatestaticfinalThreadLocal<String>threadLocal=newThreadLocal<>();privatestaticfinalThreadLocal<String>threadLocal1=newThreadLocal<>();publicstaticvoidmain(String[]args){// 线程1:存入"张三"newThread(()->{threadLocal.set("张三");threadLocal1.set("aaa");System.out.println("线程1获取:"+threadLocal.get());// 张三System.out.println("线程1获取:"+threadLocal1.get());// aaathreadLocal.remove();// 用完清理},"线程1").start();// 线程2:存入"李四"newThread(()->{threadLocal.set("李四");System.out.println("线程2获取:"+threadLocal.get());// 李四threadLocal.remove();},"线程2").start();// 主线程:没有存过,获取为nullSystem.out.println("主线程获取:"+threadLocal.get());// null}}

输出结果:线程1打印"张三",线程1的threadLocal1打印"aaa",线程2打印"李四",主线程打印"null"——三个线程互不影响。

二、ThreadLocal的线程隔离

ThreadLocal线程隔离核心:每个线程Thread对象内部,都持有一个ThreadLocalMap

  • Thread 对象持有 ThreadLocal.ThreadLocalMap 类型的成员变量
  • ThreadLocalMap 内部是一个 Entry[] 数组
  • Entry 继承自 WeakReference<ThreadLocal<?>>,key 是对 ThreadLocal 实例的弱引用,value 是存入的数据(强引用)


图片来源

1.set方法源码

set 方法先拿到当前线程的引用 Thread.currentThread(),通过 getMap(t) 获取该线程内部的 threadLocals(即 ThreadLocalMap)。如果 Map 已存在,以当前 ThreadLocal 实例为 key,将 value 存入。如果 Map 不存在(线程第一次使用 ThreadLocal),则创建一个新的 ThreadLocalMap 并存入数据。

// ThreadLocal.set() 简化源码publicvoidset(Tvalue){Threadt=Thread.currentThread();// 1. 获取当前线程ThreadLocalMapmap=getMap(t);// 2. 获取当前线程的ThreadLocalMapif(map!=null)map.set(this,value);// 3. 以当前ThreadLocal为key存值elsecreateMap(t,value);// 4. 首次创建ThreadLocalMap}ThreadLocalMapgetMap(Threadt){returnt.threadLocals;// 直接返回Thread对象的成员变量}

数据实际存储在线程Thread对象的threadLocals中,ThreadLocal本身只是一个key

2.get方法源码

get方法会先获取当前线程。然后获取当前线程的 ThreadLocalMap。以当前 ThreadLocal 实例为 key,查找对应的 Entry。找到则返回 value。如果 Map 为 null 或 Entry 为 null,调用 setInitialValue() 初始化默认值(默认返回 null,可重写 initialValue() 方法)

// ThreadLocal.get() 简化源码publicTget(){Threadt=Thread.currentThread();// 1. 获取当前线程ThreadLocalMapmap=getMap(t);// 2. 获取当前线程的ThreadLocalMapif(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);// 3. 以this为key查找Entryif(e!=null){return(T)e.value;// 4. 返回value}}returnsetInitialValue();// 5. Map未初始化则设置初始值}

三、ThreadLocalMap

ThreadLocalMap 是 ThreadLocal 的静态内部类,是一个自定义的哈希表,专门用来存储线程局部变量。它的 key 是 ThreadLocal 实例,value 是线程存储的数据。

ThreadLocalMap 的 Entry 继承自 WeakReference<ThreadLocal<?>>

staticclassEntryextendsWeakReference<ThreadLocal<?>>{/** 存入的值(强引用) */Objectvalue;Entry(ThreadLocal<?>k,Objectv){super(k);// key是弱引用value=v;// value是强引用}}
  • key 使用弱引用,如果 key 是强引用,即使我们在代码中把 ThreadLocal 对象设为 null,ThreadLocalMap 中的 Entry 仍然强引用了它,导致它无法被GC回收,从而造成内存泄漏。

  • 使用弱引用后,当 ThreadLocal 对象除了弱引用外再无其他强引用时,GC 会回收它,Entry 的 key 变为 null

1.ThreadLocalMap的哈希冲突

在 ThreadLocal 中采取的是开放地址法的方法来解决哈希冲突。当遇到哈希冲突时,会再次进行线性探测,探测的意思其实就是去寻找下一个空位。

发生哈希冲突时:

  1. 先计算目标位置 index = key.threadLocalHashCode & (len - 1)
  2. 如果该位置已被占用
    ├── key 相同 → 直接覆盖
    └── key 不同 → 向后查找下一个位置(index+1, index+2, …)
  3. 直到找到空位或相同的 key

2.过期Key的探测式清理

ThreadLocal 的设计者意识到了内存泄漏的风险,在 set、get 等方法中内置了探测式清理机制。当发现 key 为 null 的过期 Entry 时,会主动将其 value 置为 null,帮助 GC 回收。

// expungeStaleEntry(i) 的核心逻辑(简化)privateintexpungeStaleEntry(intstaleSlot){// 1. 将当前位置的 value 置为 null,Entry 置为 nulltab[staleSlot].value=null;tab[staleSlot]=null;// 2. 向后遍历,检查并清理其他过期 Entry// 3. 对未过期的 Entry 进行 rehash,填补空位// ...}

四、ThreadLocalg内存泄漏问题

内存泄漏是指程序中不再使用的对象无法被 GC 回收,始终占用内存,久而久之可能导致 OutOfMemoryError。

当 ThreadLocal 对象的外部强引用断开(比如生命周期结束或被手动置为 null)时,GC 会回收它。此时Entry 的 key 变成 null,但 value 仍然被 Entry 强引用,无法被回收。此时

  • 如果线程随之结束:Thread 对象被回收,ThreadLocalMap 也被回收,value 自然释放,没问题。
  • 如果线程长期存活(如线程池) :ThreadLocalMap 会一直存在,value 堆积越来越多,最终导致内存泄漏甚至 OOM。

最直接的办法就是使用完ThreadLocal后,在finally块中手动调用remove()方法。remove() 方法会主动将 ThreadLocalMap 中该 Entry 的 key 和 value 都清除。

// 正确的使用姿势publicclassThreadLocalBestPractice{privatestaticfinalThreadLocal<User>userHolder=newThreadLocal<>();publicvoidprocess(Useruser){userHolder.set(user);try{// 执行业务逻辑,通过 userHolder.get() 获取当前线程的用户对象}finally{userHolder.remove();// ⚠️ 一定要在 finally 中清理!}}}

五、InheritableThreadLocal:父子线程数据传递

普通的 ThreadLocal 无法在父子线程间传递数据:主线程存的值,子线程拿不到。InheritableThreadLocal 解决了这个问题,在创建子线程的瞬间,它将父线程的本地变量复制给子线程。

  • main 线程是父线程,new Thread 创建的是子线程。
publicclassInheritableThreadLocalDemo{privatestaticfinalThreadLocal<String>threadLocal=newThreadLocal<>();privatestaticfinalInheritableThreadLocal<String>inheritableTL=newInheritableThreadLocal<>();publicstaticvoidmain(String[]args){threadLocal.set("父线程-threadLocal的值");inheritableTL.set("父线程-inheritableTL的值");newThread(()->{// ThreadLocal:拿不到父线程的数据System.out.println("子线程获取ThreadLocal:"+threadLocal.get());// InheritableThreadLocal:可以拿到System.out.println("子线程获取Inheritable:"+inheritableTL.get());}).start();}}

实际应用场景

1.数据库连接管理

每个线程使用自己的 Connection,避免事务混乱和连接竞争

publicclassConnectionHolder{privatestaticfinalThreadLocal<Connection>connHolder=newThreadLocal<>();publicstaticConnectiongetConnection(){Connectionconn=connHolder.get();if(conn==null){conn=DriverManager.getConnection(url,user,password);connHolder.set(conn);}returnconn;}publicstaticvoidcloseConnection(){Connectionconn=connHolder.get();if(conn!=null){conn.close();connHolder.remove();}}}

2.用户会话信息存储

在Web应用中,将当前请求的用户信息存入ThreadLocal,整个请求链路无需显式传递用户对象

publicclassUserContext{privatestaticfinalThreadLocal<User>currentUser=newThreadLocal<>();publicstaticvoidsetUser(Useruser){currentUser.set(user);}publicstaticUsergetUser(){returncurrentUser.get();}publicstaticvoidclear(){currentUser.remove();}}

面试问题

1.一个线程可以有多个ThreadLocal对象吗?

  • 可以。线程内部的ThreadLocalMap是一个数组,不同的ThreadLocal实例通过哈希计算定位到数组的不同位置,所以一个线程可以持有多个ThreadLocal对象

2.ThreadLocalMap的key是如何计算出来的?

  • 每个 ThreadLocal 实例在创建时,都会从静态的 AtomicInteger 计数器中获取一个唯一的 threadLocalHashCode。这个值不是简单的自增,而是每次加上一个黄金分割数(0x61c88647),目的是让哈希值分布更加均匀,减少冲突。
  • 计算数组下标的公式:
inti=key.threadLocalHashCode&(table.length-1);
  • 这里 table.length 总是 2 的幂,所以 (table.length - 1) 相当于取低几位,和 HashMap 的索引计算原理一致。

3.ThreadLocal的get()方法执行时,发生GC后key是否为null?

  • 如果外部强引用被回收,那么发生GC后,Entry的key(弱引用)会被置为null,但value仍然存在。此时调用get()方法,ThreadLocalMap会通过探测式清理机制跳过key为null的Entry,然后返回setInitialValue()的默认值。

4.ThreadLocal的set()和get()方法源码中用到了什么设计模式?

  • initialValue()方法是一个protected方法,默认返回null,交给子类去重写,这是典型的模板方法模式。spring的RequestContextHolder等框架大量使用了这种模式【这里不太懂,用通俗的语言解释】

5.InheritableThreadLocal是什么?和ThreadLocal有何区别?

  • InheritableThreadLocal继承自ThreadLocal,可以在创建子线程时自动将父线程的本地变量传递给子线程。区别在于:ThreadLocal操作threadLocals,InheritableThreadLocal操作inheritableThreadLocals。传递只在子线程创建时发生一次,线程池环境下不适用。
http://www.jsqmd.com/news/715241/

相关文章:

  • VS Code容器开发环境总“失联”?深度解析2026年SSH代理链路断裂的4类新型故障(含Wireshark级诊断流程图)
  • Radxa Fogwise Airbox AI Box评测:边缘计算与AI应用实践
  • 第3篇:Sharding-JDBC(版本3.0) 入门demo,纯java 代码 【了解】
  • 2026年即墨区高端汽车真皮镀膜,哪家公司真正值得信赖? - 品牌企业推荐师(官方)
  • 终极Blazor使用指南:如何用C构建现代Web应用的完整教程
  • 【简单】在单链表中删除倒数第K个节点-Java
  • 2026年3月永余除锈除锈工艺先进吗,永余除锈,永余除锈操作简单吗 - 品牌推荐师
  • 别再用PyMOL了!5分钟教你用AlphaFold Colab免费预测自己的蛋白结构(附结果解读指南)
  • 2026年度平面抛光机去毛刺机十大厂家综合榜单 - 品牌企业推荐师(官方)
  • babyFACE韩式半永久雾眉:学生党预算也能轻松拥有 - 品牌策略主理人
  • 为什么92%的MCP插件开发者卡在“MCP server not found”?揭秘VS Code 1.88+版本TLS 1.3强制升级引发的证书链断裂真相
  • LLM Agents: 从大语言模型到自主智能体的演进与架构解析
  • 零基础玩转LFM2-2.6B:CPU推理快3倍,5分钟本地搭建聊天机器人
  • 移远L76K模组选型与实战:多系统GNSS定位在物联网项目中的优势解析
  • 图像转JSON:深度学习与OCR技术实战解析
  • 【限时解禁】VS Code Dev Containers企业级安全加固白皮书(2026 Q1 NIST SP 800-218合规对照表+CI/CD嵌入式策略)
  • 东南亚海外仓系统怎么选?东南亚跨境电商海外仓系统推荐! - 跨境小媛
  • 2026年市北区隐形车衣企业优选指南 - 品牌企业推荐师(官方)
  • 终极指南:如何用Cats Blender Plugin快速优化VRChat模型
  • CPP漫展抢票终极指南:告别手速慢,轻松搞定热门门票
  • 量化系统MMTP简介-R7
  • 从攻击者视角复盘:如何利用JBoss反序列化漏洞(CVE-2017-12149)拿到服务器权限
  • 三河高中哪个好?2026 年 10 所热门高中全对比(附官方信息表),择校看这篇就够了 - 品牌企业推荐师(官方)
  • Windows 11终极清理指南:免费开源工具Win11Debloat提升系统性能51%
  • 算法效率:复杂度原理解析
  • Matlab信号处理:FFT频谱分辨率
  • 免费音乐解锁工具Unlock-Music:打破平台限制,让音乐自由播放
  • Dism++终极指南:5分钟学会Windows系统优化与维护
  • 从一次真实的HW行动复盘讲起:我们是如何通过‘弱口令字典’快速突破内网的?
  • 为什么92%的AI团队在Docker AI Toolkit 2026 Beta测试中放弃Kubeflow?4个核心接入指标对比实测报告