ThreadLocal这点牛角尖总算是给我钻明白了

news2025/9/13 19:37:12

前言

        这个问题算是我的一个羞耻点,起源于一次面试中,面试官问ThreadLocal的底层实现是啥,我那时候一直以为ThreadLocal是一个类似于Redis一样的独立于线程外的第三方存储容器,如何底层维护了一个Map结构,以线程ID为Key,存储的数据为Value,通过线程id去找到这个存储值,那时候还很坚定这个想法,最后面试官说:em~你再回去了解下吧。我:⊙ˍ⊙

ThreadLocal 底层原理

        ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享,可以把ThreadLocal理解为商场的寄存柜,一个人只能有一个柜口,但是可以有多个寄存柜(为啥这样子比喻?看下去~)

set()源码

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
        // 当前线程自身所绑定的ThreadLocal对象
        return t.threadLocals;
}

可以看到它调用了Thread的 threadLocals 对象:

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    。。。
}

到这里我们基本可以知道ThreadLocal数据隔离的真相了,每个线程Thread都维护了自己的 ThreadLocalMap 成员变量,所以每次使用ThreadLocal.get()时都是从自己线程里面拿到自己的ThreadLocalMap 变量,拿不到别人的变量,从而实现了数据隔离。由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

// 第一次执行set操作需要调用createMap函数初始化线程内部的ThreadLocal
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

在ThreadLocalMap的构造方法里,蕴含着初始化创建table数组(封装类Entry的数组集合,Entry封装了当前的ThreadLocal对象和要存放的值)的逻辑,源码和注释如下所示:

是不是迷惑为什么ThreadLocalMap中需要存放的是个数组,不是说好了同一个ThreadLocal中只能找到一个位置存一个值吗?

答:一个线程只能在一个ThreadLocal中存放一个值没错。但是现在的问题是我ThreadLocal又不是只有一个,不要把ThreadLocal当作一个第三方缓存啊。可以把ThreadLocal理解为商场的寄存柜,一个人只能有一个柜口;商场那么多,你可以在多个商场的寄存柜中各自存放一件商品,对于你自身来说,你只需要做好记录,说我在哪个商场存放过东西就行。

  • ThreadLocal——商场的寄存柜

  • 当前线程Thread——你

  • Thread.ThreadLocalMap——存放记录清单,里面维护了个数组,支持记录多个商场的寄存柜地址,你用一个ThreadLocal来调用set / get 方法,本质上就是校验清单中寄存柜地址并执行相关操作

【解释】从上面源码中我们可以看到,数组默认大小是 16 ,设定的阈值为 0.75 倍的数组长度,并且根据传入的参数,创建了table数组中的第一个Entry元素对象(ThreadLocal对象为Key, 数据值为Value)。其中,size用来记录数组中存在的Entry元素的个数。

set插入操作其实很简单,大概流程看看:

img

get方法

这个其实没啥好说的,就是通过你调用的这个ThreadLocal对象去找当前线程中内置的ThreadLocalMap清单,找到该ThreadLocal对象通过Hash后得到的对应的那个数组下标提取数据就行,如果有

ThreadLocal-哈希冲突

ThreadLocalMap 使用开放定址法解决冲突,具体来说是线性探测法(linear probing)。线性探测法会在发生哈希冲突时,依次检查下一个位置,直到找到可用的槽位存储数据。

问:线性探测法通过后移寻找空闲位置来插入发生哈希冲突的值,那这样子的话该空闲位置不就被占用了吗?如果下一次有个元素hash后刚好到这个位置咋办?

答:在这种情况下,线性探测法会继续往后查找下一个空闲位置,并依次检查是否有其他元素占用。这个过程被称为"探测"。如果找到了下一个空闲位置,则将元素插入到该位置。如果整个哈希表都被占满了(即没有空闲位置),则说明哈希表已满,无法再插入新的元素。如果哈希表发生冲突较多,可以考虑使用其他的解决冲突的方法,如链式寻址法或开放定址法的二次探测、双重散列等。

为什么要采用线性探测法不用链式寻址法
  • 线性探测法比较适用于哈希表大小相对较小的情况,而 ThreadLocal 的规模通常比较小。因为每个线程都需要独立的空间,所以使用链式寻址法的方式,在开销和效率方面不如线性探测法。

  • 线性探测法具有较好的局部性和缓存友好性,可以使 CPU 缓存的效率得到优化,提高程序的执行效率。

ThreadLocal-内存泄露

  • 强引用:最为普通的引用方式,表示一个对象处于有用且必须的状态,如果一个对象具有强引用,则GC并不会回收它。即便堆中内存不足了,宁可出现OOM,也不会对其进行回收

  • 弱引用:表示一个对象处于可能有用且非必须的状态。在GC线程扫描内存区域时,一旦发现弱引用,就会回收到弱引用相关联的对象。对于弱引用的回收,无关内存区域是否足够,也就是说被弱引用关联的对象只能存活到下一次垃圾回收发生之前

每一个Thread维护一个ThreadLocalMap,在ThreadLocalMap中的Entry对象继承了WeakReference。其中key为使用弱引用的ThreadLocal实例,value为强引用的线程变量副本

一旦Key被回收了,key 的引用就变成了 null,就会导致这个内存永远无法被访问,造成内存泄漏。

问题:如果这个线程被回收了,那线程里面的成员变量不是都会被回收吗?就不会存在内存泄漏问题啊?

答:在实际应用中,我们一般都是使用线程池,而线程池本身是重复利用的,所以还是会存在内存泄漏的问题。

解决方法

ThreadLocal自救:

  • ThreadLocal每次调用 get、set、remove 等方法时,会顺便检查并清理掉 Entry 中 Key 为 null 的数据。

伟大的shi山创造者干涉:

  • 每次使用完 ThreadLocal 以后,主动调用 remove()方法移除数据

  • 把 ThreadLocal 声明称全局变量,使得它无法被回收

ThreadLocal 使用场景

  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束

  • 线程间数据隔离

  • 进行事务操作,用于存储线程事务信息。

  • 数据库连接,Session会话管理。

Spring框架在事务开始时会给当前线程绑定一个Jdbc connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离

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

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

相关文章

iOS性能优化

了解屏幕成像的原理。 有一个电子枪然后在很多横轴方向上 发射电子,不同横轴的电子枪根据显示器中的硬件时钟产生一系列的定时信号,以此来让电子以不同的时间发射出去 这些电子一瞬间的运动形成了一帧动画。 CPU优化: 1.文本计算优化 如果一…

DSVPN简介

定义 动态智能VPN(Dynamic Smart Virtual Private Network),简称DSVPN,是一种在Hub-Spoke组网方式下为公网地址动态变化的分支之间建立VPN隧道的解决方案。 目的 越来越多的企业希望建立Hub-Spoke方式的IPSec VPN网络将企业总部…

2760. 最长奇偶子数组 : 抽丝剥茧,图解双指针做法正确性

题目描述 这是 LeetCode 上的 「2698. 求一个整数的惩罚数」 ,难度为 「简单」。 Tag : 「双指针」、「滑动窗口」 给你一个下标从 开始的整数数组 nums 和一个整数 threshold。 请你从 nums 的子数组中找出以下标 l 开头、下标 r 结尾 ( ) 且满足以下条件的 最长子…

控制器宕机之SBC相关

简介: 本文主要以FS6500系列展开介绍,介绍深度安全故障(深度睡眠)状态的转换,以及相关寄存器值的变化过程 1.控制器什么时候会因为SBC停止工作? 窗口看门狗未及时喂狗达到故障计数最大值芯片进入某种(deep fail state)模式或LP-DFS模式或LP-Sleep模式…

阿里云的99元服务器和腾讯云的88元云服务器选择哪个?怎么选?

近日,阿里云宣布在2023年双十一优惠活动中推出了一系列降价措施,使得同配置的云服务器比腾讯云更具竞争力。这一消息不仅在云计算领域引起了轰动,更为广大互联网用户提供了更为实惠的选择。 阿里云推出99元一年的服务器,续费价格…

FreeSWITCH案例跟踪之一,sip bye发不出去

报故障的说&#xff0c;网关呼叫fs&#xff0c;网关收不到fs的sip bye Wireshark看call-flow, 是这样的&#xff1a; INVITE里面的contact是<sip:172.23.4.109:5060;transporttcp> 于是Wireshark设置过滤条件为ip.addr 172.23.4.109 and tcp.port 5060 fs tcp连网关被…

基于 HTTP Digest 与 CURL 以及 Requests 的兼容性问题:解决方案与推测原因

在使用Python库requests进行HTTP Digest认证时&#xff0c;我遇到了一个问题。当我使用requests.get()函数时&#xff0c;返回了401 Unauthorized错误&#xff0c;但是当我使用cURL命令时&#xff0c;认证成功。 解决方案&#xff1a; 确认使用的requests版本&#xff1a;我首…

大数据可视化Echarts基础快速入门

目录 一、什么是Ehcarts&#xff1f; 二、如何使用Echarts &#xff08;1&#xff09;引入Echarts的js文件 &#xff08;2&#xff09;查看文档&#xff0c;根据文档编写代码 一、什么是Ehcarts&#xff1f; 首先我们要知道什么数据可视化。什么是数据可视化&#xff1f;…

⑩【MySQL】存储引擎详解, InnoDB、MyISAM、Memory。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 存储引擎 ⑩【MySQL存储引擎】1. MySQL体系结构…

大模型会毁了初级程序员 —— 对话图灵奖得主 Joseph Sifakis | 新程序员

【编者按】GPT 系列的面世影响了全世界、各个行业&#xff0c;对于开发者们的感受则最为深切。以 ChatGPT、Github Copilot 为首&#xff0c;各类 AI 编程助手层出不穷。编程范式正在发生前所未有的变化&#xff0c;从汇编到 Java 等高级语言&#xff0c;再到今天以自然语言为特…

main.js 中的 render函数

按照之前的单组件文件中的写法&#xff0c;我们的写法应该是这样的 import App from ./App.vuenew Vue({el: #app,templete: <App></App>,components: {App}, }) 1、定义el根节点。2、注册App组件。3、渲染 templete 模板 但是在脚手架工程中&#xff0c;他是这…

C++实现高频设计模式

面试能说出这几种常用的设计模式即可 1.策略模式 1.1 业务场景 大数据系统把文件推送过来&#xff0c;根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码&#xff1a; if(type"A"){//按照A格式解析 }else if(type"B"){//按照B格式解析 …

2023最受推荐的五款项目管理工具

1、进度猫 进度猫是国内一款轻量级项目管理工具&#xff0c;适用于实时协作的团队。 以甘特图为向导&#xff0c;基于任务清单todolist&#xff0c;支持多用户协作&#xff1b; 甘特图显示具体任务清单、时间和任务的进度&#xff1b; 对未完成任务、已完成任务进行分类管…

AIGC|实践探索Langflow集成AzureOpenAI

目录 一、背景 二、AzureOpenAI介绍 三、langflow集成支持AzureOpenAI langflow集成AzureOpenAI联通改造流程&#xff1a; 四、效果演示 一、背景 langflow是一个LangChain UI&#xff0c;它提供了一种交互界面来使用LangChain&#xff0c;通过简单的拖拽即可搭建自己的实…

【文件读取/包含】任意文件读取漏洞 afr_1

1.1漏洞描述 漏洞名称任意文件读取漏洞 afr_1漏洞类型文件读取漏洞等级⭐漏洞环境docker攻击方式 1.2漏洞等级 高危 1.3影响版本 暂无 1.4漏洞复现 1.4.1.基础环境 靶场docker工具BurpSuite 1.4.2.靶场搭建 1.创建docker-compose.yml文件 version: 3.2 services: web: …

图片转excel表格怎么弄?有何密笈?

一般的软件要将图片转excel表格&#xff0c;都需要待识别的图片要有明显清晰的表格线&#xff0c;但金鸣识别则不需要这些条件的限制&#xff0c;即便是无表格线或缺少横线或竖线的图片&#xff0c;也能很好地识别成excel&#xff0c;另外&#xff0c;别的软件一般会限制文件大…

足跟痛筋膜炎最佳的治疗

足跟疼痛最常见的原因是跟腱炎。跟腱是从小腿肌肉到足跟骨的伸展部分。其他原因包括设备不当&#xff0c;走路加重脚跟的冲击力&#xff0c;鞋里面没有足弓的支撑&#xff0c;因为肥胖或怀孕脚弓变平&#xff0c;这可能会增加足跟疼痛的风险。除了跟腱炎外&#xff0c;足底筋膜…

python对数据的处理合集——字典、列表...

1.两个列表的数据对比 ①list2包含了list1,求出list2多余的值 #codingutf-8list1[1,3,5] list2[1,3,5,7,9,11] list[] for i in list2:if i not in list1:list.append(i) print(list)②求出两个列共同的值 ③两个列表合并 #第一种&#xff1a; list1 [1, 2, 3] list2 [4, …

ElasticSearch集群内存占用高?如何降低内存占用看这篇文章就够啦!(冻结索引)

ElasticSearch集群内存占用高&#xff1f;如果降低内存占用看这篇文章就够啦 一、冻结索引的介绍 经常搜索的索引被保留在内存中&#xff0c;因为重建索引和帮助高效搜索需要花费时间。另一方面&#xff0c;可能存在我们很少访问的索引。这些索引不需要占用内存&#xff0c;可…

MAC地址_MAC地址格式_以太网的MAC帧_基础知识

MAC地址 全世界的每块网卡在出厂前都有一个唯一的代码,称为介质访问控制(MAC)地址 一.网络适配器(网卡) 要将计算机连接到以太网&#xff0c;需要使用相应的网络适配器(Adapter)&#xff0c;网络适配器一般简称为“网卡”。在计算机内部&#xff0c;网卡与CPU之间的通信&…