JUC:ThreadLocal

news/2025/9/30 12:33:39/文章来源:https://www.cnblogs.com/fei1010/p/19120752

4.8 ThreadLocal

线程局部变量。

4.8.1 常见面试题

  • ThreadLocal中ThreadLocalMap的数据结构和关系?
  • ThreadLocal的key是弱引用,为什么?
  • ThreadLocal内存泄漏问题是什么?
  • ThreadLocal中最后为什么要加remove方法?

4.8.2 ThreadLocal简介

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

必须回收自定的ThreadLocal变量,尤其在线程池的场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能影响后续业务和造成内存泄漏问题i。尽量在代码中使用try-finally块进行回收

objectThreadLocal.set(userInfo);try{}finally{objectThreadLocal.remove();
}

因为每个Thread内有自己的实例副本且该副本只由当前线程自己使用

既然其它Thread不可访问,那就不存在多线程间共享的问题。

统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的

如何保证不争抢:

  • 加锁
  • ThreadLocal: 每个线程一份数据

4.8.3 ThreadLocal源码分析

// ThreadLocal类/*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/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();}/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.*/public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}/*** Removes the current thread's value for this thread-local* variable.  If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {@linkplain #set set} by the current thread* in the interim.  This may result in multiple invocations of the* {@code initialValue} method in the current thread.** @since 1.5*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {m.remove(this);}}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}...}

ThreadLocalMap实际上就是一个以ThreadLocal实例为key,任意对象为value的Entry集合。

JVM内部维护了一个线程版的Map<ThreadLocal,Value>(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中),每个线程要用到这个ThreadLocal的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量,人手一份,竟争条件被彻底消除,在并发模式下是绝对安全的变量。

4.8.4 ThreadLocaln内存泄漏问题

必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。

// 正例
objectThreadLocal.set(userInfo);
try{//...
}finally{objectThreadLocal.remove();
}

什么是内存泄漏:不再会使用的对象或者变量占用着内存,一直不被回收,就是内存泄漏

static class Entry extends WeakReference<ThreadLocal<?>> {  // 为什么使用弱引用,不用会怎么样?/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k); // 关键!将 key (k) 传递给 WeakReference<ThreadLocal<?>> 的构造函数, value = v;}
}

ThreadLocalMap 从字面上就可以看出这是一个保存ThreadLocal对象的map(以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象:

  • 第一层包装是使用 WeakReference<ThreadLocal<?>>将ThreadLocal对象变成一个弱引用的对象

  • 第二层包装是定义了一个专门的类Entry来扩展WeakReference<ThreadLocal<?>>;

引用类型 被垃圾回收(GC)的时机 是否可通过 get()方法获取对象 典型应用场景
强引用 (Strong Reference) 永不回收(只要强引用存在) 日常编程中的默认引用,用于持有需要长期存在的对象。
软引用 (SoftReference) 内存不足时(在抛出 OutOfMemoryError之前) 实现内存敏感的缓存(如图片缓存、网页缓存),在内存紧张时自动释放。
弱引用 (WeakReference) 下一次 GC 发生时(无论内存是否充足) 实现非强制的映射关系(如 WeakHashMap),防止因无用的条目积累导致内存泄漏。
虚引用 (PhantomReference) 对象被 GC 时,但其回收过程会被跟踪 get()总是返回 null 跟踪对象被垃圾回收的时机,以便在回收后执行一些清理操作,如释放堆外内存。必须和引用队列ReferenceQueue结合使用

为什么使用弱引用?

public void func(){ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("123");threadLocal.get();
}

当方法func执行完之后,栈帧销毁,强引用threadLocal也就没有了。但此时线程的ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals里某个entry的key引用还指向threadLocal这个对象。

  • 如果entry的这个key是强引用,就会导致key指向的ThreadLocal对象以及value不能被gc回收,造成内存泄漏
  • 如果entry的这个key是弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向null。

为什么Entry里value不使用弱引用?

考量维度 如果Value使用弱引用 当前设计(Value为强引用)
数据可靠性 极低:Value可能在任何时候被GC回收,导致get()返回null,业务逻辑出错。 :只要ThreadLocal强引用存在且未调用remove(),就能保证随时取到值,业务稳定。
内存管理主动性 被动依赖GC,不可预测。 主动可控:通过get/set/remove方法清理无效Entry,或在线程结束时统一释放。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链。

4.8.5 最佳实践

  • ThreadLocal.withInitial(()->初始化值) ; 一定要进行初始化,避免空指针异常
  • 建议把ThreadLocal修饰为static: ThreadLocal实例在类加载时只初始化一次
  • 用完记得手动remove

总结:

  • ThreadLocal并不解决线程间共享数据的问题
  • ThreadLocal适用于变量在线程间隔离且在方法间共享的场景
  • ThreadLocal通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
  • 都会通过expunaeStaleEntry,cleanSomeSlots,replaceStaleEntrv这三个方法回收键为 null 的 Entry对象的值(即为具体实例)以及Entry对象本身从而防止内存泄漏,属于安全加固的方法
  • 群雄逐鹿起纷争,人各一份天下安

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

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

相关文章

广义串并联图とP6790 [SNOI2020] 生成树

广义串并联图とP6790 [SNOI2020] 生成树 前置知识:广义串并联图 定义广义串并联图为不存在与 \(K_4\)(即 \(4\) 个点的完全图)同胚的子图的连通无向图(同胚是指可以通过边的放缩而互相转化的图,即 \((x\leftright…

Manim实现波浪形文字特效

本文将介绍如何使用Manim实现波浪形文字特效,通过自定义动画类让文字产生波浪般的动态效果。 1. 实现原理 波浪形文字特效的核心是通过自定义Animation类,对文本对象中的每个字符应用不同的位置偏移,从而形成波浪效…

网站开发电子书网站ip地址 转向域名

内容简要 1分析网站 2简单爬取 3进阶自定义爬取 4保存进数据库 学校基础设施太差&#xff0c;宿舍电量过低提醒虽然贴在楼下&#xff0c;但是作为低头一族&#xff0c;经常忘记看提醒导致宿舍酣战时突然黑屏&#xff0c;为了避免这种尴尬的场景以及强化PY学习&#xff0c;我决定…

JUC: synchronized与锁升级

4.10.1 面试题谈谈你对synchronized的理解 synchronized的锁升级机制是什么? 偏向锁和轻量锁有什么区别?高并发时,同步调用应该去考量锁的性能损耗。能用无锁的数据结构,就不要用锁。能用锁块,就不要锁整个方法体…

cron表达式,每月1号凌晨3点执行和每周4凌晨3点半执行

cron表达式,每月1号凌晨3点执行和每周4凌晨3点半执行cron表达式,每月1号凌晨3点执行和每周4凌晨3点半执行 1.每月1号凌晨3点执行的Cron表达式为:0 0 3 1 * ? 每个月1号 凌晨3点   0 0 3 1 * ? 和 0 0 3 1 …

学python的第8天

学python的第8天字符编码 水导链接——字符编码 水导链接——Python2和3字符编码的区别 文件基本操作 从硬盘中读取数据、写入数据 水导链接——文件基本操作 绝对路径和相对路径 水导链接——绝对路径和相对路径 文件…

2025.9.30

坐火车回家

lang / philipino / feilvbin / taglog / tajialu

s菲语 翻译magandang tanghali 下午好end

C#/.NET/.NET Core技术前沿周刊 | 第 56 期(2025年9.22-9.28)

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与…

漳州做网站建设的公司做网站 赚广告费

作为当下大语言模型的典型代表&#xff0c;ChatGPT对人类学习方式和教育发展所产生的变革效应已然引起了广泛关注。技术的快速发展在某种程度上正在“倒逼”教育领域开启更深层次的变革。在此背景下&#xff0c;教育从业者势必要学会准确识变、科学应变、主动求变、以变应变&am…

US$249 Autek IKEY820 New License for GM, Grand Cheokee and Dodge Durango Key Programming

Autek IKEY820 New License for GM, Grand Cheokee and Dodge Durango Key ProgrammingWith this license, Autek IKEY820 can support new car models as below:1. Added 2018 Buick LaCrosse Pincode and Key Progra…

Estun机器人数据断电保持问题解决方案

Estun机器人数据断电保持问题解决方案要数据断电保持: 1.变量必须为全局变量 2.用等号做赋值运算

天津港口海鲜之旅全攻略(2025最新版)

🦀 天津港口海鲜之旅全攻略(2025最新版) 天津不仅是工业重镇,更是海鲜爱好者的天堂!每年9月开海后,正是吃海鲜、出海捕鱼的黄金时节。以下是为你整理的天津港口海鲜之旅全攻略,涵盖出海码头、海鲜购买、美食推…

tomcat创建bat启动,结合任务计划实现自动重启tomcat服务 - 详解

tomcat创建bat启动,结合任务计划实现自动重启tomcat服务 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "C…

如何从安卓手机恢复手机照相机消失的相机照片?(6个高效办法)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实用指南:【论文精读】Few-Shot Object Detection with Attention-RPN and Multi-Relation Detector

实用指南:【论文精读】Few-Shot Object Detection with Attention-RPN and Multi-Relation Detectorpre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: bloc…

网站开发实战asp制作视频教程wordpress用户积分

类似问题答案我是自动化专业的学生&#xff0c;大一的时候有没有必要考计算机二级证书计算机二级 虽然不是很重要 但是考了也是有好处的 自动化的跟计算机联系非常大 二级证不怎么重要 但是C语言很重要 对于你们 希望你还是去考个二级作为学习建筑类专业的学生,如果考试计算机二…

Chromium V8类型混淆漏洞CVE-2025-10585安全分析

微软安全响应中心发布关于Chromium V8引擎类型混淆漏洞CVE-2025-10585的安全公告。该漏洞已被在野利用,基于Chromium的Microsoft Edge浏览器已通过更新修复此漏洞。本文详细介绍了漏洞影响范围和版本信息。CVE-2025-1…

US$47.5 B48 MSV90 ISN Reading via OBD Authorization for Yanhua Mini ACDP

MSV90 ISN Reading via OBD Authorization for Yanhua Mini ACDPYou are offering to pay for MSV90 authorization alone, to work with your Yanhua ACDP.If you buy both Yanhua DME B48 Integrated Interface Boar…

免费电商网站模板海尔商城网站建设维护

等差数列划分 思路&#xff1a; 经验题目要求 dp[i]表示&#xff1a;以 i 位置为结尾的所有子数组中有多少个等差数列 状态转移方程 对 dp[i] 位置&#xff0c;数列至少有三个元素&#xff0c;如果相邻三个为等差数列&#xff0c;dp[i] dp[i-1] 1; 如果相邻三个不为等差数…