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

ThreadLocal详解

什么是 ThreadLocal?​

ThreadLocal 是 Java 中的一个工具类,用于为每个线程提供独立的变量副本,使得每个线程可以独立操作自己的变量,避免多线程环境下的数据竞争问题。它的核心思想是​​线程封闭​​(Thread Confinement),即通过将变量限制在线程内部使用,实现线程安全。

ThreadLocal 的核心机制​

存储结构​​

每个线程(Thread 对象)内部维护了一个 ThreadLocalMap,键(Key)是 ThreadLocal 实例,值(Value)是该线程的局部变量副本。

// 简化的 ThreadLocal 结构
public class ThreadLocal<T> {public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = t.threadLocals;Entry e = map.getEntry(this);return (T)e.value;}
}

在这里插入图片描述
ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

数据隔离​​

不同线程访问同一个 ThreadLocal 时,实际上操作的是各自线程的 ThreadLocalMap 中的不同数据,天然线程安全。

ThreadLocal 的 API​

void set(T value)

设置当前线程的变量副本

 public void set(T value) {//1、获取当前线程Thread t = Thread.currentThread();//2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,//则直接更新要保存的变量值,否则创建threadLocalMap,并赋值ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else// 初始化thradLocalMap 并赋值createMap(t, value);}

ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化

T get()

获取当前线程的变量副本

    public T get() {//1、获取当前线程Thread t = Thread.currentThread();//2、获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);//3、如果map数据不为空,if (map != null) {//3.1、获取threalLocalMap中存储的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为nullreturn setInitialValue();}

void remove()

移除当前线程的变量副本

 public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}

initialValue()

初始化变量(需重写,默认返回null)

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}

ThreadLocal 的典型使用场景​

全局变量在线程间隔离​​

例如用户会话(Session)、数据库连接、事务上下文等。

​​线程安全的工具类​​

如 SimpleDateFormat 是非线程安全的,通过 ThreadLocal 为每个线程分配独立实例:

private static final ThreadLocal<SimpleDateFormat> dateFormat =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public String formatDate(Date date) {return dateFormat.get().format(date);
}

​ 跨方法传递隐式参数​​

避免在方法间层层传递参数,例如在日志框架中保存请求ID。

ThreadLocal 的内存泄漏问题​

原因​​

ThreadLocalMap内部维护了一个Entry[] table来存储键值对的映射关系,内存泄漏和Entry类有非常大的关系,下面是Entry的源码:

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value; // 强引用Entry(ThreadLocal<?> k, Object v) {super(k); // Key 是弱引用value = v;}
}

ThreadLocalMap中的Entry的key是弱引用指向ThreadLocal实例,而value是强引用。(弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收)。

解决方案

自动清理机制(部分场景触发)​

当调用 ThreadLocal 的 ​​get()、set() 或 remove()​​ 方法时,ThreadLocalMap 会尝试清理 Key 已被回收的 Entry(即 Key 为 null 的 Entry)。但这种清理是 ​​非彻底​​ 的:
​​触发条件​​:操作过程中发现 Key 为 null 的 Entry。
​​清理范围​​:仅清理当前操作路径上的 Entry,不会全量扫描。(从当前操作的哈希槽位(Hash Slot)开始,向后线性探测遍历​​,直到遇到 Entry 为 null 的槽位为止。在此路径上遇到的 ​​所有 Key 为 null 的 Entry​​ 会被直接清理,但其他位置的无效 Entry 可能仍残留。)
示例​​:

threadLocal.set(123); // 触发部分清理
threadLocal.get();    // 触发部分清理

显式调用 remove()​​:

使用完 ThreadLocal 后,调用 remove() 清理 Entry。
​​使用 try-finally 块​​:

try {threadLocal.set(value);// ... 业务逻辑
} finally {threadLocal.remove();
}

InheritableThreadLocal

默认情况下,子线程无法继承父线程的 ThreadLocal 变量。使用 InheritableThreadLocal 解决此问题:

private static final InheritableThreadLocal<String> inheritableThreadLocal =new InheritableThreadLocal<>();public static void main(String[] args) {inheritableThreadLocal.set("Parent Value");new Thread(() -> {System.out.println(inheritableThreadLocal.get()); // 输出: Parent Value}).start();
}
http://www.xdnf.cn/news/240895.html

相关文章:

  • 从工厂到生活:算法 × 深度学习,正在改写自动化的底层逻辑
  • Js扩展DOM、BOM、AJAX、事件、定时器
  • react学习笔记2——基于React脚手架与ajax
  • DBeaver虚拟主键会影响实际的数据库吗
  • 贝叶斯算法实战:从原理到鸢尾花数据集分类
  • Linux安装部署Postgresql数据库
  • 数字智慧方案5971丨智慧农业大数据平台解决方案(59页PPT)(文末有下载方式)
  • PostgreSQL安装部署
  • 网络安全知识问答微信小程序的设计与实现
  • 前端面试宝典---webpack原理解析,并有简化版源码
  • Leetcode刷题记录23——最小覆盖子串
  • systemd和OpenSSH
  • DeepSeek V3重磅升级!
  • 联邦学习的收敛性分析(全设备参与,不同本地训练轮次)
  • LoRA、QLoRA、LoRA+、LongRA、DoRA、MaLoRA、GaLore
  • MySQL基础关键_002_DQL(一)
  • [AI]怎么计算中文被bert模型切分的tokens数量
  • TC8:SOMEIP_ETS_021-022
  • 产品VP简历模板案例
  • # 基于 Python 和 jieba 的中文文本自动摘要工具
  • ChipCN IDE KF32 导入工程后,无法编译的问题
  • 探秘明远智睿SSD2351开发板在HMI领域的独特魅力
  • 2025第八届数字中国峰会启幕 | 思特奇以数智力量,助推数字中国建设
  • 游戏性能测试
  • C 语 言 - - - 文 件 操 作
  • vue3 动态修改系统title
  • python查看指定的进程是否存在
  • 安凯微以创新之芯,赋能万物智能互联新时代
  • k8s术语值ReplicaSet
  • navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)