“深入剖析ThreadLocal原理:从多线程数据隔离到内存泄漏防范“

1.在没有ThreadLocal遇到的问题:

在多线程编程领域,多个线程同时访问同一个变量时,数据一致性成为关键挑战。为防止修改数据时出现覆盖问题,传统解决方案是采用加锁机制,让线程排队依次访问共享变量。然而,这种 “排队” 策略不可避免地消耗时间,在高并发场景下,性能损耗尤为明显,成为程序效率提升的瓶颈。

加锁机制的局限性

加锁的本质是强制线程排队,确保同一时刻仅有一个线程操作共享变量。但这一过程伴随着线程的等待与调度,增加了额外的时间开销。尤其在竞争激烈的场景中,频繁的加锁、解锁操作会严重影响程序的执行效率,无法满足高性能需求。

“空间换时间” 的巧妙突破

从 “空间换时间” 的视角出发,可尝试为每个线程复制一份变量副本。如此一来,每个线程仅操作自己的专属副本,彼此互不干扰,既保障了数据安全,又彻底消除了等待时间。这一思路在生活中有诸多类似场景:

  • 火车站商务候车厅:为特定乘客(线程)提供独立空间(副本),减少公共区域(共享资源)的拥挤与等待。
  • 主卧独立厕所:家庭成员(线程)各自使用专属空间(副本),避免共用厕所(共享资源)时的排队。

在编程领域,这种思路典型地体现在 ThreadLocal 等机制中。它为每个线程提供专属的变量存储,线程仅需操作自己的副本,无需与其他线程竞争,在数据安全与执行效率间找到了完美平衡。

2.对副本变量特征进行分析:

图中对副本变量特征进行分析:

  • 变量呈现 “key→value” 格式,而 Map 结构正是以键值对形式存储数据,因此适用 Map 结构存储。
  • 涉及存值、取值、删除值操作,这些是 Map 结构的常见操作,能够很好地支持。
  • 存储数量不多,使用 Map 结构不会造成过大开销,较为合适。

综上,选择 Map 结构存储副本变量,是因它匹配 “key→value” 格式,支持相关操作,且在数据量不大时能有效工作。

3.Map的底层数据结构

图中内容主要解析 Map 底层数据结构的设计思路:

  • 计算机底层数据结构包含数组与链表,数组可通过下标直接获取值。
  • Map 以 key→value 形式组织数据,若能让 key 映射到一个数字(如通过哈希函数计算下标),就可借助数组存储,实现快速访问 value。这是许多 Map 实现(如 HashMap)的底层逻辑基础,通过哈希将 key 映射到数组位置,结合链表或红黑树处理冲突,在存储和查询效率上达到优化。

4.Map的实现

图中介绍了 Map 实现中的两个关键问题:哈希冲突与数组扩容,针对哈希冲突,主要有链表法和开放寻址法两种解决方案,具体解析如下:

  • 链表法
    • 实现:为散列表每个位置创建链表存储元素,采用 “数组 + 链表” 形式。
    • 优点:处理冲突简单,无堆积现象,平均查找长度短;链表结点动态申请,适合构造表时长度不确定的情况;删除结点操作易于实现,只需删除链表上相应结点。
    • 缺点:指针需要额外空间,当结点规模较小时,开放定址法更节省空间。
    • 现实场景类比:在操场开元旦晚会,每个班级有固定位置,后来的班级排在后面(若链表过长,如后面太远看不见,可能通过红黑树优化)。
  • 开放寻址法
    • 实现:一旦发生冲突,就寻找下一个空的散列地址存储记录,只要散列表足够大,总能找到空地址。
    • 优点:当结点规模较小时,相对节省空间。
    • 缺点:容易产生堆积问题,不适用于大规模数据存储;散列函数的设计对冲突影响大,插入时可能多次冲突;若删除的元素是多个冲突元素中的一个,需对后面元素作处理,实现较复杂。
    • 现实场景类比:网吧包间,你去之前跟朋友说定一个位置(若指定包间被占,顺着找下一个空的),朋友按此方法找你。

综上,两种方法各有优劣,实际应用中需根据场景(如数据规模、操作特点等)选择合适的冲突解决策略,以优化 Map 的性能。




ThreadLocal的官方实现

工具类特性ThreadLocal 类的一个实例绑定一个变量,提供存值、取值、删除值三个操作方法,方便对线程本地变量进行管理。

底层实现ThreadLocal 内部实现 Map 的底层数据存取,采用开放寻址法解决 Map 中的哈希冲突问题,并进行了优化,确保数据存储与获取的高效性。

数据存储位置:将副本变量的数据存放在线程自身中,每次数据操作直接针对线程自身的属性,实现线程间数据隔离。
总结:ThreadLocal 本身不存储值,而是访问当前线程 ThreadLocalMap 里存储的数据副本,有效实现了线程间的数据隔离,避免多线程环境下的数据竞争问题。

        这句话揭示了 ThreadLocal 的核心机制: ThreadLocal 本身并非实际存储数据的容器,而是作为一个 “桥梁” 或 “访问入口” 存在。每个线程内部都有一个专属的 ThreadLocalMap(类似于一个小型的键值对存储结构),当通过 ThreadLocal 调用 set() 方法存储值时,实际上是将数据以 ThreadLocal 自身作为键,存入当前线程的 ThreadLocalMap 中;调用 get() 方法时,也是从当前线程的 ThreadLocalMap 中获取与该 ThreadLocal 关联的值。

       举个简单例子,就像每个线程有一个 “私人储物柜”(ThreadLocalMap),ThreadLocal 就像这个柜子的 “钥匙”,通过这把 “钥匙” 操作的始终是当前线程自己 “柜子” 里的数据,与其他线程的 “柜子” 无关,从而实现了线程间的数据隔离。这样,每个线程都在自己的 ThreadLocalMap 中维护变量的副本,ThreadLocal 并不直接存储值,只是提供了对当前线程内 ThreadLocalMap 中数据副本的访问方式。

Threadlocal与Thread的关系

ThreadLocal 类代码

public class ThreadLocal<T> {// 构造函数public ThreadLocal() {}// 获取当前线程绑定的变量值public T get() {Thread t = Thread.currentThread(); // 获取当前线程ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap// 省略后续从map中获取值的代码return null;}// 设置当前线程绑定的变量值public void set(T value) {Thread t = Thread.currentThread(); // 获取当前线程ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap// 省略后续在map中设置值的代码if (map != null)map.set(this, value);elsecreateMap(t, value);}// 获取线程的ThreadLocalMapThreadLocalMap getMap(Thread t) {return t.threadLocals; // 每个线程Thread都有threadLocals属性,类型是ThreadLocalMap}// 创建新的ThreadLocalMap并绑定到线程void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); // 将新的ThreadLocalMap设置到线程的threadLocals属性}// ThreadLocal的内部类ThreadLocalMap,实现数据存储static class ThreadLocalMap {// 内部Entry类,继承WeakReference,键为ThreadLocalstatic class Entry extends WeakReference<ThreadLocal<?>> {Object value; // 存储的值Entry(ThreadLocal<?> k, Object v) {super(k); // 调用父类WeakReference的构造函数,传入ThreadLocal作为引用value = v; // 设置值}}private Entry[] table; // 存储Entry的数组// ThreadLocalMap的构造函数ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY]; // 初始化数组int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 计算哈希位置table[i] = new Entry(firstKey, firstValue); // 在计算出的位置创建Entry}}
}

Thread 类代码

public class Thread implements Runnable {// 省略其他代码ThreadLocal.ThreadLocalMap threadLocals = null; // 每个线程都有自己的 ThreadLocalMap 实例,用于存储线程本地变量ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 用于支持线程变量继承的 ThreadLocalMap// 省略其他代码
}

类图关系


Thread 提供存储场所(ThreadLocalMap)

每个 Thread 类内部都有一个类型为 ThreadLocal.ThreadLocalMap 的成员变量 threadLocals。

ThreadLocalMap 是 ThreadLocal 的静态内部类,本质是一个特殊的 “容器”,用于存储线程局部变量。

当线程通过 ThreadLocal 操作变量时,实际是在操作该线程自身的 threadLocals(即 ThreadLocalMap)。例如,调用 threadLocal.set(value) 时,会以 threadLocal 自身为键,将 value 存入当前线程的 threadLocals 中,实现数据的线程内隔离存储。

每个线程(Thread)内部维护一个 ThreadLocalMap,这是 ThreadLocal 存储数据的核心结构。

  • ThreadLocalMap 的作用
    • 每个线程的 ThreadLocalMap 是一个哈希表,用于存储该线程的线程局部变量。
    • 键(Key)是 ThreadLocal 对象,值(Value)是线程的变量副本。
    • 通过 ThreadLocalMap,每个线程可以独立地存储和访问自己的数据,而不会与其他线程冲突。

ThreadLocal 提供操作接口(set、get、remove 等方法)

ThreadLocal 类提供了一系列简洁的方法,封装了对 ThreadLocalMap 的操作细节:

  • set(T value) 方法

public void set(T value) {  Thread t = Thread.currentThread(); // 获取当前线程  ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap  if (map != null)  map.set(this, value); // 以当前 ThreadLocal 为键,存入值  else  createMap(t, value); // 若 ThreadLocalMap 不存在,创建并存储  
}  
  • 该方法先获取当前线程及其 ThreadLocalMap,若 ThreadLocalMap 已存在,直接以当前 ThreadLocal 为键存储值;若不存在,则创建新的 ThreadLocalMap 并存储。

  • 流程
    1. 获取当前线程的 ThreadLocalMap
    2. 如果 map 存在,直接将当前 ThreadLocal 对象作为键,传入的 value 作为值存入 map
    3. 如果 map 不存在(线程首次调用 set),则创建新的 ThreadLocalMap 并初始化数据。
  • 关键点
    • 每个线程的 ThreadLocalMap 是独立的,因此不同线程的 set 操作互不影响。
    • ThreadLocal 对象本身作为键,确保每个线程只能访问自己的数据副本。
  • get() 方法

public T get() {  Thread t = Thread.currentThread();  ThreadLocalMap map = getMap(t);  if (map != null) {  ThreadLocalMap.Entry e = map.getEntry(this);  if (e != null) {  @SuppressWarnings("unchecked")  T result = (T)e.value;  return result;  }  }  return setInitialValue(); // 若未获取到值,设置初始值并返回  
}  
  • 该方法从当前线程的 ThreadLocalMap 中查找以当前 ThreadLocal 为键的值并返回,若未找到则设置初始值。

  • 流程
    1. 获取当前线程的 ThreadLocalMap
    2. 如果 map 存在,尝试根据当前 ThreadLocal 对象作为键查找对应的值。
    3. 如果找到,返回值;否则调用 setInitialValue() 初始化默认值。
  • 关键点
    • get 方法始终操作当前线程的 ThreadLocalMap,确保线程隔离。
    • 如果未显式调用 set,首次 get 会触发 initialValue() 初始化(默认返回 null)。
  • remove() 方法

public void remove() {  ThreadLocalMap m = getMap(Thread.currentThread());  if (m != null)  m.remove(this); // 从当前线程的 ThreadLocalMap 中删除当前 ThreadLocal 对应的键值对  
}  
  • 该方法从当前线程的 ThreadLocalMap 中删除以当前 ThreadLocal 为键的键值对,避免内存泄漏。

  • 流程
    1. 获取当前线程的 ThreadLocalMap
    2. 如果 map 存在,移除当前 ThreadLocal 对象对应的键值对。
  • 关键点
    • 必须手动调用 remove():避免内存泄漏。
    • 如果线程池中的线程长期存活,不清除 ThreadLocalMap 中的值会导致残留数据污染后续任务。

通过这些方法,开发者无需关心 ThreadLocalMap 的底层实现(如哈希冲突处理、数组扩容等),直接通过 ThreadLocal 即可便捷地管理线程局部变量,实现数据的存储、获取和删除,同时保证线程间数据的独立性。

综上,Thread 通过 threadLocals 成员变量提供存储结构,ThreadLocal 通过 setgetremove 等方法封装操作逻辑,二者协作实现了高效、安全的线程局部变量管理。

ThreadLocalMap 的内部结构

ThreadLocalMapThreadLocal 的静态内部类,它是一个定制化的哈希表,专门用于存储线程局部变量。以下是其核心设计:

Entry 结构

static class Entry extends WeakReference<ThreadLocal<?>> {Object value; // 实际存储的值Entry(ThreadLocal<?> k, Object v) {super(k); // Key 是弱引用(防止内存泄漏)value = v;}
}
  • Key 是弱引用
    • ThreadLocal 对象作为键时,使用 WeakReference 包装。
    • 当 ThreadLocal 对象不再被强引用时,GC 会回收它,避免内存泄漏。
  • Value 是强引用
    • 值对象不会被自动回收,必须显式调用 remove() 清理。

Thread 类和 ThreadLocal 类在 Java 中是两个独立的类,它们没有继承关系。Thread 类是 Java 中用于创建和管理线程的类,而 ThreadLocal 类是用于为每个使用它的线程都单独存储一份独立的变量副本。

协作关系

Thread 和 ThreadLocal 是协作关系,Thread 为 ThreadLocal 提供存储数据的场所(ThreadLocalMap),ThreadLocal 为 Thread 提供了方便的操作接口(如 set、get、remove 方法)来管理线程局部变量。这种协作实现了线程间数据的隔离,每个线程可以独立地操作自己的局部变量,互不干扰。


源码解析

ThreadLocalMap提供的方法



ThreadLocalMap提供的清理方法




面试题简答







内存泄漏与强弱引用问题




强、弱引用面试题简答












本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903834.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读懂 Vue3 路由:从入门到实战

在构建现代化单页应用&#xff08;SPA&#xff09;时&#xff0c;Vue3 凭借其简洁高效的特性成为众多开发者的首选。 而 Vue3 路由&#xff08;Vue Router&#xff09;则是 Vue3 生态中不可或缺的一部分&#xff0c;它就像是单页应用的 “导航地图”&#xff0c;帮助用户在不同…

Mac M1安装 Docker Desktop 后启动没反应

Mac M1安装 Docker Desktop 后启动没反应 如果在 Mac M1 上安装 Docker&#xff0c;那最好的选择是安装 Docker Desktop但是今天重新安装 Docker Desktop 后&#xff0c;发现点击图标启动怎么也没反应&#xff0c;经过排查后发现换个版本安装就好了&#xff0c;可能是最新的版…

快速上手c语言

快速上手c语言 快速上手c语言关于学c语言的一些信息杂谈第一个C语言程序通过命令行运行c程序Dev-c5.11Visual Studio系列产品 数据类型变量、常量定义变量的方法变量的命名变量的分类变量的使用变量的作用域和生命周期常量 操作符简单介绍语句选择语句循环语句 数组数组定义数组…

Nginx核心功能及正则表达

目录 一&#xff1a;正向代理 1&#xff1a;编译安装nginx &#xff08;1&#xff09;安装支持软件 &#xff08;2&#xff09;创建运行用户、组和日志目录 &#xff08;3&#xff09;编译安装nginx &#xff08;4&#xff09;添加nginx系统服务 2&#xff1a;配置正向代…

npm命令介绍(Node Package Manager)(Node包管理器)

文章目录 npm命令全解析简介基础命令安装npm&#xff08;npm -v检插版本&#xff09;初始化项目&#xff08;npm init&#xff09;安装依赖包&#xff08;npm install xxx、npm i xxx&#xff09;卸载依赖包&#xff08;npm uninstall xxx 或 npm uni xxx、npm remove xxx&…

【Linux】Linux基础概念

一些比较重要的使用Linux的前情提要。 部分经验来源于网络&#xff0c;若有侵权请联系我删除&#xff0c;主要是做笔记的时候忘记写来源了&#xff0c;做完笔记很久才写博客。 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目录 1 Shell命令参数 2 系统变量…

阿里开源Qwen3:大语言模型的新突破

一、模型概览&#xff1a;丰富的模型家族 Qwen3 系列包含了 2 款混合专家&#xff08;MoE&#xff09;模型与 6 款密集&#xff08;Dense&#xff09;模型&#xff0c;参数量覆盖范围极广&#xff0c;从 0.6B 一直延伸至 235B 。其中&#xff0c;旗舰模型 Qwen3 - 235B - A22B…

数字智慧方案5856丨智慧环保综合解决方案(50页PPT)(文末有下载方式)

资料解读&#xff1a;智慧环保综合解决方案 详细资料请看本解读文章的最后内容。 随着城市化进程的加速和环境问题的日益严峻&#xff0c;智慧环保成为提升城市环境管理水平的重要手段。本文将对智慧环保综合解决方案进行详细解读&#xff0c;探讨其在实际应用中的需求、解决…

基于ssm的网盘管理系统(全套)

一、系统架构 前端&#xff1a;vue | element-ui 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat | nodejs 二、代码及数据库 三、功能介绍 01. 注册 02. 登录 03. 管理员-首页 04. 管理员-个人中心 …

PostgreSQL 的 VACUUM 与 VACUUM FULL 详解

PostgreSQL 的 VACUUM 与 VACUUM FULL 详解 一、基本概念对比 特性VACUUMVACUUM FULL定义常规维护操作&#xff0c;清理死元组激进重组操作&#xff0c;完全重写表数据锁级别不阻塞读写(共享锁)排他锁(阻塞所有操作)空间回收只标记空间为可用&#xff0c;不返还OS空间返还操作…

复刻低成本机械臂 SO-ARM100 舵机配置篇(WSL)

视频讲解&#xff1a; 复刻低成本机械臂 SO-ARM100 舵机配置篇&#xff08;WSL&#xff09; 飞特舵机 组装之前需要配置舵机的ID&#xff0c;如下的网址为舵机的资料&#xff0c;实际上用不到&#xff0c;但可以mark在这里 Software-深圳飞特模型有限公司 User Guide里面可以…

Tailwind CSS实战技巧:从核心类到高效开发

使用 Kooboo平台 训练实战技巧&#xff0c;无需配置安装&#xff0c;直接引入CDN就可以在线练习了&#xff01;具体操作流程&#xff1a;进入Kooboo后&#xff0c;选择创建空白站点 -> 站点开发 -> 控制面板 -> 页面 ->新建普通页面 -> 编写代码 一、核心布局类…

【LINUX操作系统】线程操作

了解了线程的基本原理之后&#xff0c;我们来学习线程在C语言官方库中的写法与用法。 1. 常见pthread接口及其背后逻辑 1.1 pthread_create 与线程有关的函数构成了⼀个完整的系列&#xff0c;绝⼤多数函数的名字都是以“pthread_”打头的 • 要使⽤这些函数库&#xff0c;…

【AI面试准备】Azure DevOps沙箱实验全流程详解

介绍动手实验&#xff1a;通过 Azure DevOps 沙箱环境实操&#xff0c;体验从代码提交到测试筛选的全流程。如何快速掌握&#xff0c;以及在实际工作中如何运用。 通过 Azure DevOps 沙箱环境进行动手实验&#xff0c;是快速掌握 DevOps 全流程&#xff08;从代码提交到测试筛选…

VulnHub-DC-2靶机

主机发现 sudo arp-scan -l 以sudo管理员权限扫描本地活动ip地址 Interface: eth0, type: EN10MB, MAC: 08:00:27:22:46:4f, IPv4: 192.168.252.230 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.252.6 4c:5f:70:74:3c:3b …

藏语英语中文机器翻译入门实践

&#x1f3af; 项目目标&#xff1a; 输入藏文句子&#xff0c;自动翻译成英文和中文&#xff08;或输入中文&#xff0c;翻译为英文和藏文&#xff09;。 &#x1f50d; 技术与原理简介 机器翻译&#xff08;Machine Translation, MT&#xff09;是人工智能中自然语言处理&a…

【阿里云大模型高级工程师ACP习题集】2.9 大模型应用生产实践(上篇)

练习题 【单选题】在自然语言处理的法务咨询场景中,以下哪种模型选择最为合适? A. 通用大语言模型 B. 经过数学领域微调的模型 C. 面向法律领域训练的模型 D. 视觉模型 【多选题】以下哪些属于模型非功能性需求?( ) A. 模型对不同语言的支持能力 B. 模型的响应速度要求 C.…

WPF之ProgressBar控件详解

文章目录 1. ProgressBar控件简介2. ProgressBar的基本属性和用法2.1 基本属性2.2 基本用法2.3 代码中修改进度 3. 确定与不确定模式3.1 确定模式&#xff08;Determinate&#xff09;3.2 不确定模式&#xff08;Indeterminate&#xff09; 4. 在多线程环境中更新ProgressBar4.…

IntelliJ IDEA 保姆级安装教程(附安装包)

文章目录 一、下载二、安装三、启动 一、下载 Ultimate 2021.1.1 - Windows x64 (exe) 二、安装 三、启动 首次安装启动 非首次安装启动

Performance API 性能上报

以下是关于 Performance API 性能上报的基本知识点总结: 一、性能监控核心指标体系 1. 关键性能指标(Web Vitals) 指标标准采集方式健康阈值LCP (最大内容绘制)测量加载性能PerformanceObserver≤2.5sFID (首次输入延迟)测量交互响应PerformanceObserver≤100msCLS (累积布…