详解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冲突,需要利用线性探测,循环遍历一轮数组。
相关文章:
详解Java ThreadLocal
个人博客 详解Java ThreadLocal | iwts’s blog Java ThreadLocal ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。 TreadLocal存储模型 ThreadLocal的静态…...
Unable to parse response body for Response{requestLine=PUT
1 异常信息: Caused by: java.lang.RuntimeException: Unable to parse response body for Response{requestLinePUT /an_path_statistic_log/_doc/11?timeout1m HTTP/1.1, hosthttp://192.168.3.60:9200, responseHTTP/1.1 200 OK}at org.springframework.data.e…...
GitHub的原理及应用详解(六)
本系列文章简介: GitHub是一个基于Git版本控制系统的代码托管平台,为开发者提供了一个方便的协作和版本管理的工具。它广泛应用于软件开发项目中,包括但不限于代码托管、协作开发、版本控制、错误追踪、持续集成等方面。 GitHub的原理可以简单…...
基于PHP+MySQL组合开发的微信小程序分销商城源码系统 分销商城+积分商城+多商户 功能强大 带完整的安装代码包以及搭建教程
系统概述 在当今数字化商业时代,拥有一个强大而多功能的分销商城系统对于企业的发展至关重要。本文将重点介绍基于 PHPMySQL 组合开发的微信小程序分销商城源码系统,它融合了分销商城、积分商城和多商户等功能,不仅功能强大,还提…...
kafka-消费者组偏移量重置
文章目录 1、消费者组偏移量重置1.1、列出所有的消费者组1.2、查看 my_group1 组的详细信息1.3、获取 kafka-consumer-groups.sh 的帮助信息1.4、 偏移量重置1.5、再次查看 my_group1 组的详细信息 1、消费者组偏移量重置 1.1、列出所有的消费者组 [rootlocalhost ~]# kafka-…...
一书读懂Python全栈安全,剑指网络空间安全
写在前面 通过阅读《Python全栈安全/网络空间安全丛书》,您将能够全面而深入地理解Python全栈安全的广阔领域,从基础概念到高级应用无一遗漏。本书不仅详细解析了Python在网络安全、后端开发、数据分析及自动化等全栈领域的安全实践,还紧密贴…...
原生js实现拖拽改变元素顺序
代码展示如下: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>…...
以果决其行,只为文化的传承
从他们每一个人的身上,我们看到传神的东西,就是他们都能用结果,去指引自己前进的方向,这正是我要解读倪海厦老师的原因,看倪海厦2012年已经去世,到现在已经十几年时间了,但是我们看现在自学中医…...
Flutter 中的 SizedOverflowBox 小部件:全面指南
Flutter 中的 SizedOverflowBox 小部件:全面指南 在 Flutter 的布局世界中,SizedOverflowBox 是一个相对独特的小部件,它允许子组件溢出其父组件的界限,同时保持父组件的尺寸不变。这在某些特定的布局场景下非常有用,…...
图像视频智能抹除修复解决方案,适应性强,应用广泛
行车录制、现场拍摄等过程中,往往会出现一些难以避免的瑕疵——遮挡物、无关人员、甚至是意外的光线变化,这些都可能影响到视频与图像的质量,降低其观赏性和专业性。 美摄科技,作为行业领先的图像视频智能处理专家,深…...
20240521(代码整洁和测试入门学习)
测试: 1.测试工程师、测试工具开发工程师、自动化测试工程师。 python: 1、发展背景和优势; 2、开始多需的工具 interpreter(解释器) refactor(重构) 2、变量和注释的基础语法 3、输入输出 i 1 for i in range(1, 11): print(i, end ) 不换行打印…...
git中忽略文件的配置
git中忽略文件的配置 一、在项目根目录下创建.gitignore文件二、配置规则如果在配置之前已经提交过文件了,要删除提交过的,如何修改,参考下面的 一、在项目根目录下创建.gitignore文件 .DS_Store node_modules/ /dist# local env files .env…...
如何进行前端职业规划
目录 找准自身定位 未来发展方向 扬长避短很有效 你的出处并不能代表什么 将目标放长放远 职业发展中面临的选择 全栈 or 纯前端? ToB or ToC 赚钱 or 个人成长? 分析每个阶段的需求 为什么不可以一边赚钱一边做喜欢的事情呢 我们还没离开校园的时候,就已经知道要…...
GD32F103系列单片机片上FLASH和ARM介绍
本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布: 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转: 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…...
Ansible自动化运维中的Setup收集模块应用详解
作者主页:点击! Ansible专栏:点击! 创作时间:2024年5月22日13点14分 💯趣站推荐💯 前些天发现了一个巨牛的🤖人工智能学习网站,通俗易懂,风趣幽默…...
再次学习History.scrollRestoration
再次学习History.scrollRestoration 之前在react.dev的源代码中了解到了这个HIstory的属性,当时写了一篇笔记来记录我对它的理解,现在看来还是一知半解。所以今天打算重新学习一下这个属性,主要从属性以及所属对象的介绍、使用方法࿰…...
python PyQt5 数字时钟程序
效果图: 概述 本文档将指导您如何使用Python的PyQt5库创建一个简单的时钟程序。该程序将显示当前时间,并具有以下特性: 始终在最前台显示。窗口可拖动。鼠标右键点击窗口可弹出退出菜单。时间标签具有红色渐变效果。窗口初始化时出现在屏幕…...
骨传导耳机哪个品牌值得入手?精选五大不容错过的王者品牌推荐!
尽管骨传导耳机作为新型蓝牙耳机问世不久,但凭借其独特的传音方式和舒适的佩戴体验,已经迅速在市场上崭露头角,赢得了广大音乐爱好者和运动达人的青睐。然而,随着骨传导耳机热度增高,市场上开始出现一些品质参差不齐的…...
Vue.js|项目安装
根据Vue脚手架创建出来的项目目录: 运行项目: 控制台中输入下面的命令: npm run serve 修改vue项目运行端口: 前往vue.config.js中添加下面的代码: devServer: {port: 7000} 接着前往控制台输入Ctrlc关闭项目&…...
多线程新手村4--定时器
定时器是日常开发中很常见的组件,定时器大家可能不知道是干什么的,但是定时炸弹肯定都听过,定个时间,过一段时间后bomb!!!爆炸 定时器的逻辑和这个一样,约定一个时间,这…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
