深度解析ThreadLocal:底层原理、数据隔离与内存泄漏解决

news2024/9/21 21:53:57

前言

        这个问题算是我的一个羞耻点,起源于一次面试中,面试官问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插入操作其实很简单,大概流程看看:

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可以避免多次传递,打破层次间的约束

  • 线程间数据隔离

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

  • 数据库连接,通过线程池管理连接可以提高性能和资源利用率。在这种情况下,可以使用 ThreadLocal 来存储每个线程中的数据库连接,确保每个线程都有自己独立的连接对象,避免多线程间的共享和竞争。

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

CASE说明

        一个常见的使用场景是在 Web 应用中,需要在每个用户请求的线程中存储用户会话信息。比如,在一个使用了线程池的 Web 服务器中,每个用户请求会被分配到一个线程中处理,如果直接将用户会话信息存储在普通的成员变量中,就会导致线程安全问题。这时就可以使用 ThreadLocal 来存储用户会话信息,确保每个线程中都有自己独立的数据副本,避免线程之间的数据混乱。

        举个实际业务例子,比如在一个电子商务网站中,用户登录之后需要存储用户的购物车信息。使用 ThreadLocal 可以在用户登录成功后将购物车信息存储在 ThreadLocal 中,在用户每次请求时从 ThreadLocal 中获取购物车信息进行处理,确保每个用户在线程中都有自己独立的购物车数据,不会被其他线程干扰。这样既保证了数据的线程安全,又避免了频繁的数据传递和同步操作。

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

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

相关文章

专题一——双指针算法

原理:将数组进行区间划分,通过指针(下标)的移动实现题目所要求的区间(数组分块) (实现代码统一是C) 建议在做题与看题解时要自己反复模拟这个实现的过程,以后在做题做到类似的题才能举一反三&am…

QT6实现创建与操作sqlite数据库及读取实例(一)

一.Qt为SQL数据库提供支持的基本模块(Qt SQL) Qt SQL的API分为不同层: 驱动层 SQL API层 用户接口层 1.驱动层 对于Qt 是基于C来实现的框架,该层主要包括QSqlDriver,QSqlDriverCreator,QSqlDriverCreatorBase,QSqlPlug…

Linux第78步_使用原子整型操作来实现“互斥访问”共享资源

使用原子操作来实现“互斥访问”LED灯设备,目的是每次只允许一个应用程序使用LED灯。 1、创建MyAtomicLED目录 输入“cd /home/zgq/linux/Linux_Drivers/回车” 切换到“/home/zgq/linux/Linux_Drivers/”目录 输入“mkdir MyAtomicLED回车”,创建MyA…

Python从 Google 地图空气质量 API 获取空气污染数据

获取给定位置当前的空气质量 让我们开始吧!在本节中,我们将介绍如何使用 Google 地图获取给定位置的空气质量数据。您首先需要一个 API 密钥,可以通过您的 Google Cloud 帐户生成该密钥。他们有90 天的免费试用期,之后您将为您使用的 API 服务付费。在开始大量拨打电话之前…

51单片机中断信号的种类及应用场景

在嵌入式系统中,中断是一种重要的事件处理机制,它可以在程序执行的任何时候暂停当前任务,转而执行与之相关的特殊任务或事件。51单片机作为一种常见的微控制器,其中断功能在各种应用中起着关键作用。然而,对于初学者和…

一、SpringBoot基础搭建

本教程主要给初学SpringBoot的开发者,通过idea搭建单体服务提供手把手教学例程,主要目的在于理解环境的搭建,以及maven模块之间的整合与调用 源码:jun/learn-springboot 以商城项目为搭建例子,首先计划建1个父模块&…

部署单节点k8s并允许master节点调度pod

安装k8s 需要注意的是k8s1.24 已经弃用dockershim,现在使用docker需要cri-docker插件作为垫片,对接k8s的CRI。 硬件环境: 2c2g 主机环境: CentOS Linux release 7.9.2009 (Core) IP地址: 192.168.44.161 一、 主机配…

GPT-4 VS Claude3、Gemini、Sora:五大模型的技术特点与用户体验

【最新增加Claude3、Gemini、Sora、GPTs讲解及AI领域中的集中大模型的最新技术】 2023年随着OpenAI开发者大会的召开,最重磅更新当属GPTs,多模态API,未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义,不亚…

CTF题型 md5考法例题汇总

CTF题型 md5考法相关例题总结 文章目录 CTF题型 md5考法相关例题总结一.md5弱字符相等()[SWPUCTF 2021 新生赛]easy_md5 二.md5强字符相等()1)文件相等[2024 qsnctf 擂台赛 easy_md5]2)字符相等[安洵杯 2019]easy_web 三.md5哈希长度扩展攻击[NPUCTF2020]ezinclude文件包含利用…

深入技术细节:放弃Spring Security,自己实现Token权限控制!

最近做了个项目,大家都知道很多的项目都是在自己手上原本的框架内进行业务开发。但是甲方爸爸的这个项目需要交付原代码,并且要求框架逻辑简单清晰,二次开发简易上手。 那不是要重新从0到1写一套框架吗? 试着先给甲方爸爸报一下…

美食杂志制作秘籍:引领潮流,引领味蕾

美食杂志是一种介绍美食文化、烹饪技巧和美食体验的杂志,通过精美的图片和生动的文字,向读者展示各种美食的魅力。那么,如何制作一本既美观又实用的美食杂志呢? 首先,你需要选择一款适合你的制作软件。比如FLBOOK在线制…

Java微服务分布式事务框架seata的TCC模式

🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 往期热门专栏回顾 专栏…

教大家使用vue实现 基础购物车。

首先我要知道一点 其能够数据变化的 都用{{}}来进行渲染类似 来看一下实现效果 实现思路 : 1,引进 vue.js 2,setup 将声明变量 方法放在setup里面 3,用响应式声明 ref() 或rective声明 可以声明对象等等 let 也…

AST学习入门

AST学习入门 1.AST在线解析网站 https://astexplorer.net/ 1.type: 表示当前节点的类型,我们常用的类型判断方法t.is********(node)**,就是判断当前的节点是否为某个类型。 2**.start**:表示当前节点的开始位置 3.end:当前节点结束 4.loc : 表示当前节点所在的行…

产品推荐 | 基于Xilinx FPGA XC5VFX100T的6U VPX视频叠加板卡

01、产品概述 本板卡是基于Xilinx FPGA XC5VFX100T的6U VPX视频叠加板卡。主要用于视频叠加板具有多种高清图形输入接口,可实现其中两路高清视频信号的开窗显示和叠加显示功能;或者输出和输入图形接口的转换。 02、物理特性 ● 尺寸:6U CPC…

【文献阅读】AlphaFold touted as next big thing for drug discovery — but is it?

今天来精读2023年10月发在《Nature》上的一篇新闻:AlphaFold touted as next big thing for drug discovery — but is it? (nature.com)https://www.nature.com/articles/d41586-023-02984-w Questions remain about whether the AI tool for predicting protein …

Python Windows系统 虚拟环境使用

目录 1、安装 2、激活 3、停止 1、安装 1)为项目新建一个目录(比如:目录命名为learning_log) 2)在终端中切换到这个目录 3)执行命令:python -m venv ll_env,即可创建一个名为ll…

常用的6个的ChatGPT网站,国内可用!

GPTGod 🌐 链接: GPTGod 🏷️ 标签: GPT-4 免费体验 支持API 支持绘图 付费选项 📝 简介:GPTGod 是一个功能全面的平台,提供GPT-4的强大功能,包括API接入和绘图支持。用户可以选择免…

成都百洲文化传媒有限公司电商新浪潮的领航者

在当今电商行业风起云涌的时代,成都百洲文化传媒有限公司以其独特的视角和专业的服务,成为了众多商家争相合作的伙伴。今天,就让我们一起走进百洲文化的世界,探索其背后的成功密码。 一、百洲文化的崛起之路 成都百洲文化传媒有限…

短视频矩阵系统---php7.40版本升级自研

短视频矩阵系统---php7.40版本升级自研 1.部署及搭建 相对于其他系统,该系统得开发及部署难度主要在各平台官方应用权限的申请上,据小编了解,目前抖音短视频平台部分权限内侧名额已满,巧妇难为无米之炊,在做相关程序…