详解Java ThreadLocal

个人博客

详解Java ThreadLocal | iwts’s blog

Java ThreadLocal

ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

TreadLocal存储模型

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。
这样看太抽象了,具体数据结构可以从存储方法开始看。

根据set方法探究ThreadLocal存储模型

看上去简单粗暴,先是获取线程,然后看能不能获取数据结构,能获取就直接set,获取不到就初始化。

那么这里指向了一个核心方法:ThreadLocalMap是怎么获得的。

这个东西竟然是存储在线程对象里面的:

等于说TreadLocal的核心内部类,是存在一个线程中的。
这个时候再回过头来看ThreadLocalMap:

这个Entry继承了WeakReference,代表他是一个弱引用,而其泛型则是ThreadLocal,那么说明了单个Entry节点,底层是一个kv结构,key是ThreadLocal的弱引用,value是一个Object。

但是这个只是Entry节点,ThreadLocalMap的底层并不是KV结构,而是一个Entry数组。

这里再继续看set()方法是怎么处理的:

可以看出来,Entry的key就是这个具体的ThreadLocal对象,value是具体设置的value。

那么这个下标怎么计算的?

这里与计算比较类似HashMap,保证数组长度是2的幂,这样与运算直接就是取模了,具体内容参考下面。

综上,可以得到ThreadLocal究竟是怎么存的:

注意,key存储的是ThreadLocal的弱引用,那么扩展到JVM内存模型下:

ThreadLocal本身的引用是正常放在栈中的,但是由于WeakReference,所以key对ThreadLocal的引用是虚引用。这也是OOM的主要原因。

Thread中threadLocals

整个ThreadLocal的数据都是维护在单个线程的属性中,所以保证了线程间的隔离,因为这玩意就是线程自己的一个变量。

ThreadLocalMap

ThreadLocal的内部类,其中定义了Entry节点,存储具体的数据。而本身提供了Entry数组,用于存储线程下全部的ThreadLocal数据。

此外提供了一系列数组操作方法,以及扩容等操作。

Entry

内部类Entry,继承了WeakReference,其内部设置属性value,实现了一个类似KV结构的数据结构,保证了key是ThreadLocal的一个弱引用,value是具体的数值。

ThreadLocalMap的维护

getMap()

等于从线程中获取线程本身的ThreadLocalMap对象。

ThreadLocalMap的初始化

java.lang.ThreadLocal#createMap

直接走了构造方法:

初始化了Entry数组。初始化数组长度为16,并且设置了扩容阈值threshold。具体看下面。

扩容机制

类似HashMap扩容,初始化长度为16,后续扩容的时候直接*2,保证长度是2的幂。只有在超过阈值的时候才会扩容。

阈值threshold的维护,为当前数组长度的2/3。

跟HashMap一样,扩容后需要对旧值重新hash,定位到确定的下标。相比HashMap简单多了。

ThreadLocal对象hash定位下标

算法看起来还是比较简单的。了解HashMap这里没啥难度,由于长度必然是2的幂,这样与计算也是和取模的效果是一样的。

主要是这个threadLocalHashCode是什么。

这里算是一个小优化了,主要获取方法:

这里走CAS执行一次自增,增量为常量0x61c88647。

这样,0x61c88647是斐波那契散列乘数,那么后续的自增导致hash出来的结果分布会比较均匀,可以很大程度上避免hash冲突,下面是15次操作获取的hash值

hash冲突

由于ThreadLocalMap的底层是一个数组,设计上也不想太复杂,所以没有采用HashMap的拉链法,而是采用线性探测来解决hash冲突。

非常简单,其实就是+1,看是否越界,越界设置0。其实这里改成取模更加简洁。

ThreadLocal弱引用与内存泄漏

回到最上面Entry的设计,继承了WeakReference,表明了key的对象是个弱引用。

那么在ThreadLocal执行完毕后,如果没有及时清除,就会导致ThreadLocal对象没有具体的引用对象。那么就会被GC回收掉。

参考上面的图,GC回收的是堆,堆中的这个key,他引用的是ThreadLocal,也就是说这个key的引用,是会被GC回收掉的。

而Entry中value是个普通的Object,那么value本身是不会被回收的。
这样在大量操作后,就会导致value无法回收,导致内存泄漏。

解决方案

ThreadLocal使用结束后及时remove()。

而JDK为了解决这个问题,本身也或多或少在帮助我们回收。

remove()的时候会主动触发,而get()、set()在执行的时候也会间接执行:expungeStaleEntry()方法。

这个方法会主动清除所有Entry中key为null的对象。

强引用解决方案

比较奇葩,感觉用的不多。

即利用static修饰ThreadLocal,这样就能保证ThreadLocal对象是强引用,从根本上解决问题。

为什么选择用弱引用

依然是OOM问题。

由于ThreadLocalMap的生命周期跟Thread一样长,如果是强引用,那么手动删除对应key的情况下,仍然会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal被清理后key为null,对应的value在下一次ThreadLocalMap调用set、get、remove的时候可能会被清除。

弱引用虽然会导致OOM,但是强引用不仅会导致OOM,还会更多。

ThreadLocal API

都比较简单感觉没什么好聊的。

需要注意的是hash冲突问题,由于hash冲突的解决方案是线性探测,所以在get的时候,需要对比key是否一致,如果不一致可能是hash冲突,需要利用线性探测,循环遍历一轮数组。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用WebStorm如何调试Vue代码

大家好,我是咕噜铁蛋。今天,我想和大家分享一下如何使用WebStorm这款强大的IDE(集成开发环境)来调试Vue代码。Vue.js作为现代前端开发的利器,其强大的组件化开发能力和简洁的API深受开发者喜爱。然而,随着项…

D2Admin:企业中后台产品前端集成方案的探索与实践

D2Admin:企业中后台产品前端集成方案的探索与实践 摘要:随着企业信息化建设的不断深入,中后台管理系统的前端技术选型与集成方案成为了关键。D2Admin作为一款完全开源免费的前端集成方案,通过采用最新的前端技术栈,提…

pdf编辑器推荐,这三款软件十分好用!

在数字化时代,PDF文档因其跨平台、易阅读的特性,成为了我们工作、学习、生活中不可或缺的一部分。然而,如何高效、便捷地编辑PDF文档,却成为许多人面临的难题。今天,就为大家推荐三款十分好用的PDF编辑器,让…

C++学习---string模拟实现(2)

1.随机插入一个字符串 (1)insert函数插入一个字符的方法我们在之前的模拟实现里面已经搞过了,那个里面要注意的是这个全体向后挪动的循环过程,这个里面我们要实现的是插入字符串的模拟实现; (2&#xff0…

【算法】前缀和——前缀和

本题主要用一个模板题目来说明前缀和的基本思想,有需要借鉴即可。 目录 1.题目2.前缀和2.1题目分析2.2前缀和算法第一步,先预处理一个前缀数组第二步,由题计算得结果 3.代码示例4.总结 1.题目 题目链接:LINK 这个题目可以用暴力…

c 的库函数有哪些

C语言的库函数非常丰富,涵盖了多种功能,为程序员提供了大量的工具来完成各种任务。以下是一些主要的C语言库函数及其分类: 标准输入输出函数: printf():用于输出格式化的数据到标准输出设备。scanf():用于…

数字化农业新时代:图扑农林牧综合监控平台

利用图扑自研 HT for Web GIS 产品,结合遥感技术,构建可交互式的农林牧数据分析平台。该平台围绕地块总览、播种分析、牛只管理、设备查询四个维度,对地区的全貌、农场、村集体分布以及相应的环境进行多样化的可视化展示和进行数据支持&#…

网站报价明细

随着互联网的快速发展和普及,网站建设已经成为越来越多企事业单位必备的基础设施之一。作为企业展示形象和运营业务的重要平台,网站对于企业发展起着举足轻重的作用。因此,网站报价明细在企业进行网站建设时尤为重要。 网站报价明细是指在网站…

Java多线程(02)

一、如何终止线程 终止线程就是要让 run 方法尽快执行结束 1. 手动创建标志位 可以通过在代码中手动创建标志位的方式,来作为 run 方法的执行结束条件; public static void main(String[] args) throws InterruptedException {boolean flag true;Thr…

邦注科技三机一体除湿干燥机在工业中的应用

三机一体除湿干燥机在工业中的应用广泛且重要,其结合了传统除湿机、冷凝器和加热器的功能,具有节能、环保、方便等特点。以下是关于三机一体除湿干燥机在工业中应用的详细解析: 一、应用领域 电子制造行业:在半导体、集成电路和…

超清高帧,成像升级 | SWIR短波红外相机500万像素992芯片

博图光电5MP短波红外相机,搭载了索尼IMX992 SenSWIR传感器,支持5.2MP分辨率,适合探测波长在400nm-1700nm波段的可见光和短波红外光,有效面积和透光率得到提升,内置TEC制冷片,实现了像素尺寸和图像均匀性方面…

重学java 49 增强for

知之俞明,则行之越笃;行之愈笃,则知之愈益; —— 24.5.28 一、基本使用 1.作用: 遍历集合或者数组 2.格式: for(元素类型 变量名:要遍历的集合名或者数组名) 变量名就是代表的每一个元素 3.快捷键: 集合名或者数组名.for package …

AI大模型如何“开窍”?算法、数据与架构的三重奏

一、算法创新 1. 探索新的学习范式 自监督学习:利用未标注数据让模型自我学习,提高模型的泛化能力。元学习:让模型学会如何学习,以便在不同任务之间快速迁移。强化学习:通过试错与奖励机制,使模型在与环境…

外贸仓库管理软件:海外仓效率大幅度提升、避免劳动力积压

随着外贸业务的不断发展,如何高效管理外贸仓库,确保货物顺利流转,订单顺利处理,就变得非常重要。 现在通常的解决方案都是通过引入外贸仓库管理软件,也就是我们常说的海外仓WMS系统来解决。 今天我们就系统的探讨一下…

langchian进阶二:LCEL表达式,轻松进行chain的组装

LangChain表达式语言-LCEL,是一种声明式的方式,可以轻松地将链条组合在一起。 你会在这些情况下使用到LCEL表达式: 流式支持 当你用LCEL构建你的链时,你可以得到最佳的首次到令牌的时间(输出的第一块内容出来之前的时间)。对于一些链&#…

Rust最新版安装(v1.78.0+)

系统:Windows 11 专业版 23H2rustc:1.78.0 配置环境变量和设置配置文件 新建文件夹“C:\Rust\Rustup”和“C:\Rust\Cargo”。【以管理员身份运行】打开CMD 设置系统环境变量,如下设置RUSTUP_DIST_SERVER,其余同理 C:\Windows\S…

钡铼PLC集成BL121PO协议网关优化电子制造产线的生产效率

PLC转OPC UA协议转换网关BL121PO在电子制造产线中的优化应用,可以显著提高生产效率,促进生产线的智能化和信息化发展。本文将从以下几个方面进行阐述: 提高设备间通信效率:PLC转OPC UA协议转换网关BL121PO通过高效的协议转换&…

Keras深度学习框架第十九讲:在 KerasCV 中使用CutMix、MixUp 和 RandAugment 图像增强技术

1、绪论 1.1 图像增强的主流方法 CutMix CutMix 是一种图像增强技术,它通过从另一幅图像中随机裁剪一个区域并粘贴到当前图像上来创建新的训练样本。同时,标签也会按照两个图像中裁剪区域的比例进行混合。这种方法有助于模型学习如何处理部分遮挡的情…

VScode代码片段自动转图标

注:在VScode编辑器中,编辑html、vue等文件时,特定代码片段(token/xxx’等)自动转图标显示,按住“ctrl鼠标左键”还可跳转“https://icones.js.org/collections”,个人感觉干扰代码编写&#xff…

SD Flash介绍

作为一家专业生产存储芯片及存储卡的原厂,我们时常收到客户关于SD Flash的各种技术问题。MK米客方德将详细解答关于SD Flash的常见问题,助您更好地了解这一重要存储技术。 SD Flash是一种常见的存储卡技术,广泛应用于各种便携式设备中&#x…