当前位置: 首页 > news >正文

玩转ThreadLocal

前言

ThreadLocal想必都不陌生,当多线程访问同一个共享变量时,就容易出现并发问题,为了保证线程安全,我们需要对共享变量进行同步加锁,但这又带来了性能消耗以及使用者的负担,那么有没有可能当我们创建一个共享变量时,每个线程对其访问的时候访问的都是自己线程的变量呢?没错那就是ThreadLocal。

ThreadLocal使用

举个简单例子:
比如实现一些数据运算的操作,过程中可能需要借助一个临时表去处理数据,临时表有一列存的每一次的执行ID,执行完成根据此次的执行ID进行删除临时表数据。可以使用一个ThreadLocal来存储当前线程的执行ID。

public class DataSyncServiceImpl {private final Logger logger = LoggerFactory.getLogger(DataSyncServiceImpl.class);private static final ThreadLocal<String> execLocalId = ThreadLocal.withInitial(()->new String());@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 借助临时表进行数据运算操作* 临时表字段(id,execution_id)*/public void calculateData(String key){try {execLocalId.set(UUID.randomUUID().toString());calculate();check();System.out.println("同步数据...");}finally {destory();}}private void calculate(){try {System.out.println("数据运算");String execId = execLocalId.get();//...Thread.sleep(1000L);} catch (InterruptedException e) {logger.error("执行异常!",e);}}private void check(){try {System.out.println("数据运算");String execId = execLocalId.get();//...Thread.sleep(1000L);} catch (InterruptedException e) {logger.error("执行异常!",e);}}private void destory(){//根据execution_id删除临时表数据StringBuffer sql = new StringBuffer();sql.append("delete from temp_table where execution_id = ?");jdbcTemplate.update(sql.toString(),execLocalId.get());execLocalId.remove();}
}

这样的话保证了每一个请求线程都有自己的执行ID,清除数据时互不影响。

ThreadLocal实现原理

进入Thread类,可以看到这样两个变量,threadLocals和inheritableThreadLocals,他们都是ThreadLocalMap类型,而ThreadLocalMap是一个类似Map的结构。默认情况下两个变量都为null,当前线程调用set或者get时才会创建。也就是说ThreadLocal变量其实是存在调用线程的内存空间中。每个Thread线程都保存了一个共享变量的副本。

1、threadLocals:当前线程的ThreadLocal变量
2、inheritableThreadLocals:解决子线程不能访问父线程中的ThreadLocal变量

ThreadLocalMap

ThreadLocalMap是一个key为ThreadLocal本身,值为存入的value,对于不同的线程,每次获取副本时,别的线程不能获取到当前线程的副本值,形成了隔离。

Thread和ThreadLocal的关系

在这里插入图片描述

Set方法源码分析

    * 设置当前线程对应的ThreadLocal的值* @param value 将要保存在当前线程对应的ThreadLocal的值*/public void set(T value) {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 判断map是否存在if (map != null)// 存在则调用map.set设置此实体entry,this这里指调用此方法的ThreadLocal对象map.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中createMap(t, value);}/*** 获取当前线程Thread对应维护的ThreadLocalMap * * @param  t the current thread 当前线程* @return the map 对应维护的ThreadLocalMap */ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/***创建当前线程Thread对应维护的ThreadLocalMap * @param t 当前线程* @param firstValue 存放到map中第一个entry的值*/void createMap(Thread t, T firstValue) {//这里的this是调用此方法的threadLocalt.threadLocals = new ThreadLocalMap(this, firstValue);}

执行步骤:

获取当前线程,根据当前线程获取到ThreadlocalMap,即threadLocals;
如果获取到的Map不为空,则设置value,key为调用此方法的ThreadLocal引用;
如果Map为空,则先调用createMap创建,再设置value。

Get方法源码分析

     * 返回当前线程中保存ThreadLocal的值* 如果当前线程没有此ThreadLocal变量,* 则它会通过调用{@link #initialValue} 方法进行初始化值* @return 返回当前线程对应此ThreadLocal的值*/public T get() {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null) {// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体eThreadLocalMap.Entry e = map.getEntry(this);// 对e进行判空 if (e != null) {@SuppressWarnings("unchecked")// 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值T result = (T)e.value;return result;}}/*初始化 : 有两种情况有执行当前代码第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry*/return setInitialValue();}/*** 初始化* @return the initial value 初始化后的值*/private T setInitialValue() {// 调用initialValue获取初始化的值// 此方法可以被子类重写, 如果不重写默认返回nullT value = initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 判断map是否存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中createMap(t, value);// 返回设置的值valuereturn value;}

执行步骤:

1、获取当前线程,获取此线程对象中维护的ThreadLocalMap对象;
2、如果Map不为空,则通过当前调用的ThreadLocal对象获取Entry;
3、判断Entry不为空,则直接返回value;
4、Map或Entry为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为Key和Value创建一个新的Map。

内存泄漏

在这里插入图片描述

从ThreadLocal整体设计上我们可以看到,key持有ThreadLocal的弱引用,GC的时候会被回收,即Entry的key为null。但是当我们没有手动删除这个Entry或者线程一直运行的前提下,存在有强引用链 threadRef->currentThread->threadLocalMap->entry -> value ,value不会被回收,导致内存泄漏。

出现内存泄漏的情况:

1、没有手动删除对应的Entry节点信息,value一直存在。
2、ThreadLocal 对象使用完后,对应线程仍然在运行。

避免内存泄露:

1、使用完ThreadLocal,调用其remove方法删除对应的Entry。
2、对于第二种情况,因为使用了弱引用,当ThreadLocal 使用完后,key的引用就会为null,而在调用ThreadLocal 中的get()/set()方法时,当判断key为null时会将value置为null,这就就会在jvm下次GC时将对应的Entry对象回收,从而避免内存泄漏问题的出现。
在这里插入图片描述

总结

本文主要讲解了ThreadLocal的作用及基本用法,以及ThreadLocal的实现原理和基础方法,注意事项。最后,用ThreadLocal一定要记得用完remove!

相关文章:

玩转ThreadLocal

前言 ThreadLocal想必都不陌生&#xff0c;当多线程访问同一个共享变量时&#xff0c;就容易出现并发问题&#xff0c;为了保证线程安全&#xff0c;我们需要对共享变量进行同步加锁&#xff0c;但这又带来了性能消耗以及使用者的负担&#xff0c;那么有没有可能当我们创建一个…...

亚马逊二审来袭,跨境电商传统验证算法真的靠谱吗?

多个大卖突遭二审 已有卖家账号被封 近期有不少卖家在论坛上反映称自己收到了亚马逊的二次视频验证邮件。 邮件上称&#xff1a; 卖家必须要完成额外的身份审查&#xff0c;才有资格在亚马逊继续销售商品&#xff1b;亚马逊要求卖家出示注册时提交的身份证原件和营业执照原件…...

微信小程序|基于小程序+云开发制作一个租房小程序

经济发展的同时伴随着大批人群的流动,租房需求一直是持久不衰的话题,如何租好房,好租房,跟随此文一起制作一个租房小程序,让租房不再困难。 一、小程序1. 创建小程序2. 首页3. 房源列表页4. 房源详情页5. 个人中心页</...

2.4 群辉驱动:多网口,系统网络只能识别两个网口 解决教程

所需工具下载&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1CMLl6waOuW-Ys2gKZx7Jgg?pwdchct提取码&#xff1a;chct安装的黑群晖华硕z490i主板自带一个i225 2.5G&#xff0c;后又插了一个4口8125B四口网卡&#xff0c;发现控制面板->网络->网络界面 只识别了其…...

Android正确使用资源res文件

观看此文注意首先有的UI改颜色&#xff0c;没用&#xff0c;发现无法更改按钮背景颜色。我的AS下载的是最新版本&#xff0c;Button按钮的背景颜色一直都是亮紫色&#xff0c;无法更改。为什么呢&#xff1f;首先在你的清单文件中看你应用的是哪个主题。我现在用的是这个可能你…...

5分钟搭建第一个k8s集群

急速上手Minikube搭建单节点 k8s集群实战什么是Minikube?环境准备安装步骤一.安装Docker1.安装yml2.设置阿里云镜像3.查看可安装的docker版本4. 安装docker5. 查看docker版本6.配置docker开机自启动7. 启动docker, 查看docker 启动状态二.安装k8s1.配置镜像源2.安装kubectl3.安…...

【MySQL】查询操作(基础篇)

目录 1、查询操作(Retrieve) 1.1 全列查询 1.2 指定列查询 1.3 查询字段为表达式 1.4 别名 1.5 去重&#xff1a;DISTINCT 1.6 排序&#xff1a;ORDER BY 1.7 条件查询&#xff1a;WHERE 1.8 分页查询 1、查询操作(Retrieve) 查询操作算的上是 SQL 中最复杂的操作了…...

工程管理系统+spring cloud 系统管理+java 系统设置+二次开发

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

MyBatisPlus Study Notes

文章目录1 MyBatisPlus概述1.1 MyBatis介绍1.2 MyBatisPlus特性2 标准数据层开发2.1 MyBatisPlus的CRUD操作API2.2 分页功能接口实现2.2.1 config&#xff08;配置层&#xff09;拦截器实现2.2.2 Dao(Mapper)数据访问层&#xff08;CRUD&#xff09;操作2.2.3 Junit单元测试进行…...

【Vu3 测试篇】自动化测试

一、为什么需要测试 自动化测试能够预防无意引入的 bug&#xff0c;并鼓励开发者将应用分解为可测试、可维护的函数、模块、类和组件。这能够帮助你和你的团队更快速、自信地构建复杂的 Vue 应用。与任何应用一样&#xff0c;新的 Vue 应用可能会以多种方式崩溃&#xff0c;因…...

Android system实战 — Android R(11) 第三方apk权限

Android system实战 — 第三方apk权限问题0. 前言1. 源码实现1.1 主要函数1.2 修改思路和实现1.2.1 修改思路1.2.2 方案一1.2.3 方案二0. 前言 最近在调试时遇到了第三方apk申请运行时权限&#xff0c;以及signature级别 install 权限不允许赋予给第三方apk&#xff0c;虽然这是…...

面试总结1

这里写目录标题什么是ORM&#xff1f;为什么mybatis是半自动的ORM框架&#xff1f;动态sqlJDBC步骤&#xff1a;jdbc的缺点&#xff1a;JDBC,MyBatis的区别&#xff1a;MyBatis相比JDBC的优势缓存一级缓存一级缓存在下面情况会被清除二级缓存最近在面试&#xff0c;发现了许多自…...

【Hello Linux】程序地址空间

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍下进程地址空间 程序地址空间程序地址空间语言中的程序地址空间矛盾系统中的程序地址空间为什么要有进程地址空间思维导图总结…...

电脑崩溃蓝屏问题如何重装系统

电脑是我们日常生活和工作中必不可少的工具&#xff0c;但在使用过程中&#xff0c;难免会遇到各种问题&#xff0c;例如系统崩溃、蓝屏、病毒感染等&#xff0c;这些问题会严重影响我们的使用体验和工作效率。而小白一键重装系统可以帮助我们快速解决这些问题&#xff0c;本文…...

《商用密码应用与安全性评估》第一章密码基础知识1.2密码评估基本原理

商用密码应用安全性评估&#xff08;简称“密评”&#xff09;的定义&#xff1a;在采用商用密码技术、产品和服务集成建设的网络与信息系统中&#xff0c;对其密码应用的合规性、正确性、有效性等进行评估 信息安全管理过程 相关标准 国际:ISO/IEC TR 13335 中国:GB/T …...

【编程基础之Python】7、Python基本数据类型

【编程基础之Python】7、Python基本数据类型Python基本数据类型整数&#xff08;int&#xff09;基本的四则运算位运算比较运算运算优先级浮点数&#xff08;float&#xff09;布尔值&#xff08;bool&#xff09;字符串&#xff08;str&#xff09;Python数据类型变换隐式类型…...

Kakfa详解(一)

kafka使用场景 canal同步mysqlelk日志系统业务系统Topic kafka基础概念 Producer: 消息生产者&#xff0c;向kafka发送消息Consumer: 从kafka中拉取消息消费的客户端Consumer Group: 消费者组&#xff0c;消费者组是多个消费者的集合。消费者组之间互不影响&#xff0c;所有…...

图解LeetCode——剑指 Offer 12. 矩阵中的路径

一、题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…...

particles在vue3中的基本使用

第三方库地址 particles.vue3 - npm 1.安装插件 npm i particles.vue3 npm i tsparticles2.在main.js中引入 import Particles from particles.vue3 app.use(Particles) // 配置相关的文件常用 api particles.number.value>粒子的数量particles.number.density粒子的稀密…...

04 Android基础--RelativeLayout

04 Android基础--RelativeLayout什么是RelativeLayout&#xff1f;RelativeLayout的常见用法&#xff1a;什么是RelativeLayout&#xff1f; 相对布局&#xff08;RelativeLayout&#xff09;是一种根据父容器和兄弟控件作为参照来确定控件位置的布局方式。 根据父容器定位 在相…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...