【并发编程】ThreadLocal详解与原理

📫作者简介:小明Java问道之路2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。

           

🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人

🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家

         

 🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~ 


🍅 文末获取联系 🍅  👇🏻 精彩专栏推荐订阅收藏 👇🏻

专栏系列(点击解锁)

学习路线(点击解锁)

知识定位

🔥Redis从入门到精通与实战🔥

Redis从入门到精通与实战

围绕原理源码讲解Redis面试知识点与实战

🔥MySQL从入门到精通🔥

MySQL从入门到精通

全面讲解MySQL知识与企业级MySQL实战

🔥计算机底层原理🔥

深入理解计算机系统CSAPP

以深入理解计算机系统为基石,构件计算机体系和计算机思维

Linux内核源码解析

围绕Linux内核讲解计算机底层原理与并发

🔥数据结构与企业题库精讲🔥

数据结构与企业题库精讲

结合工作经验深入浅出,适合各层次,笔试面试算法题精讲

🔥互联网架构分析与实战🔥

企业系统架构分析实践与落地

行业最前沿视角,专注于技术架构升级路线、架构实践

互联网企业防资损实践

互联网金融公司的防资损方法论、代码与实践

🔥Java全栈白宝书🔥

精通Java8与函数式编程

本专栏以实战为基础,逐步深入Java8以及未来的编程模式

深入理解JVM

详细介绍内存区域、字节码、方法底层,类加载和GC等知识

深入理解高并发编程

深入Liunx内核、汇编、C++全方位理解并发编程

Spring源码分析

Spring核心七IOC/AOP等源码分析

MyBatis源码分析

MyBatis核心源码分析

Java核心技术

只讲Java核心技术

本文目录

本文导读

一、ThreadLocal是什么

二、ThreadLocal的数据结构

三、ThreadLocal源码解析 

1、 ThreadLocal的set()方法

2、 ThreadLocal的get()方法

3、 ThreadLocal的remove()方法

四、ThreadLocal使用场景

五、ThreadLocal内存泄露原因

六、如何正确的使用ThreadLocal

七、ThreadLocal为什么不将key设置为强引用

总结


本文导读

本文讲解ThreadLocal是什么、ThreadLocal的数据结构以及ThreadLocal源码set()/get()/remove()解析,ThreadLocal使用场景,如何正确的使用ThreadLocal,ThreadLocal内存泄露原因。

一、ThreadLocal是什么

ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

ThreadLocal 提供了线程本地的实例。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

二、ThreadLocal的数据结构

ThreadLocal是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。 

1、每个Thread线程内部都有一个Map(ThreadLocalMap)

2、Map里面存储ThreadLocal对象(key)和线程的变量副本(value)

3、Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

4、对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离互不干扰。

三、ThreadLocal源码解析 

1、 ThreadLocal的set()方法

ThreadLocal  set赋值的时候首先会获取当前线程thread,获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。

public void set(T value) {
	Thread t = Thread.currentThread(); // 1、获取当前线程
	
	// 2、获取线程中的threadLocalMap ,如果threadLocalMap不为空直接更新要保存的变量值
	// 否则创建threadLocalMap 并赋值
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);  // 初始化thradLocalMap 并赋值
}

ThreadLocalMap 是 ThreadLocal 的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> { // Java弱引用
		Object value;

		Entry(ThreadLocal<?> k, Object v) {
			super(k);
			value = v;
		}
	}
}

2、 ThreadLocal的get()方法

public T get() {
	Thread t = Thread.currentThread(); // 1、获取当前线程

	ThreadLocalMap map = getMap(t);   // 2、获取当前线程的ThreadLocalMap

	if (map != null) { // 3、如果map数据不为空,获取threalLocalMap中存储的值
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}

	// 如果是数据为null则初始化,TheralLocalMap中存放key为threadLocal值为null
	return setInitialValue();
}

3、 ThreadLocal的remove()方法

remove方法直接将ThrealLocal对应的值从当前相差Thread中的ThreadLocalMap中删除, 

public void remove() {
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
		m.remove(this);
}

四、ThreadLocal使用场景

ThreadLocal 适用于如下两种场景:1、每个线程需要有自己单独的实例;2、实例需要在多个方法中共享,但不希望被多线程共享。

1、存储用户Session(不同线程获取到的用户信息不一样)

2、数据库连接,处理数据库事务

3、数据跨层传递

4、Spring使用ThreadLocal解决线程安全问题

五、ThreadLocal内存泄露原因

Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。

主要两个原因:1、没有手动删除这个Entry;2、CurrentThread 当前线程依然运行

解决方案:1、只要在使用完下ThreadLocal,调用remove方法删除对应的Entry,就能避免内存泄漏。

2、由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null,也即entry(null,value),那这个entry对应的value永远无法访问到。如果ThreadLocal场景采用线程池,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。

综上, ThreadLocal 内存泄漏的根源是:如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏。

六、如何正确的使用ThreadLocal

1、将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露

2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

七、ThreadLocal为什么不将key设置为强引用

如果key设计成强引用且没有手动remove(),ThreadLocal ref被回收了,但是因为threadLocalMap的Entry强引用了threadLocal(key就是threadLocal), 造成ThreadLocal无法被回收。

当前线程始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry,Entry就不会被回收( Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏,也就是说ThreadLocalMap中的key使用了强引用是无法完全避免内存泄漏的

弱引用比强引用可以多一层保障弱引用的 ThreadLocal 会被回收,对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏。

总结

本文讲解ThreadLocal是什么、ThreadLocal的数据结构以及ThreadLocal源码set()/get()/remove()解析,ThreadLocal使用场景,如何正确的使用ThreadLocal,ThreadLocal内存泄露原因。

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

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

相关文章

Redis整数集合

前言 整数集合(intset)是集合键的底层实现之一&#xff0c;当一个集合只包含整数值元素&#xff0c;并且这个集合的元素数量不多时&#xff0c;Redis就会使用整数集合作为集合键的底层实现。 一. 整数集合的实现 1.1 结构 整数集合(intset)是Redis用于保存整数值的集合抽象数据…

【Linux】进程间通信——进程间通信的介绍和分类、管道、匿名管道、命名管道、匿名管道与命名管道的区别

文章目录 进程间通信1.进程间通信的介绍1.1目的和发展 2.进程间通信分类3.管道3.1匿名管道3.1.1匿名管道的原理&#xff08;文件角度&#xff09;3.1.2匿名管道的原理&#xff08;内核角度&#xff09;3.1.3管道读写规则3.1.4管道特点 3.2命名管道3.2.1创建命名管道3.2.2命名管…

【计算机网络学习之路】TCP socket编程

文章目录 前言一. 服务器1. 初始化服务器2. 启动服务器 二. 客户端三. 多进程服务器结束语 前言 本系列文章是计算机网络学习的笔记&#xff0c;欢迎大佬们阅读&#xff0c;纠错&#xff0c;分享相关知识。希望可以与你共同进步。 本篇博客基于UDP socket基础&#xff0c;介绍…

【SpringCloud】认识微服务、服务拆分以及远程调用

SpringCloud 1.认识微服务 1.1单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署 单体架构的优缺点&#xff1a; 优点&#xff1a; 架构简单&#xff0c;部署成本低 缺点&#xff1a; 耦合度高&#xff08;维护困难&#x…

设备状态监测与故障诊断系统的作用

随着工业生产的发展和技术的进步&#xff0c;设备状态监测与故障诊断系统在工业领域中扮演着越来越重要的角色。这一系统通过实时监测设备的状态和参数&#xff0c;及时发现潜在的故障&#xff0c;并提供预警信号&#xff0c;以降低生产中断、提高安全性和维护效率。以下将详细…

Django 模型和Admin站点管理(三)

一、定义模型 &#xff08;1&#xff09; 创建模型类&#xff0c;必须要继承自 models.Model from django.db import models# Create your models here. #设计数据库 #创建模型 class UserModel(models.Model):namemodels.CharField(max_length30) #对应于SQL name varchar(30…

慕尼黑电子展Samtec Demo | 回环测试带来Samtec产品组合优异表现

【摘要/前言】 大家好&#xff01;Electronica虎家展台Demo系列回来咯。 实践出真知&#xff0c;再好的纸面数据都不如来一场实际的测试和演示。Samtec团队始终在努力为客户带来卓越的产品和优质服务。而这其中&#xff0c;Demo演示的存在至关重要。演示过程可以为大家带来了…

ubuntu编译sqlite3并使用

SQLite3是一种轻量级的关系型数据库管理系统&#xff0c;它是在C语言基础上实现的。SQLite3具有许多优点&#xff0c;例如&#xff1a; 1.灵活&#xff1a;它可以在多种操作系统上运行&#xff0c;并且可以将多个数据库文件合并成一个文件。 2.易于使用&#xff1a;SQLite3使用…

“云浮云福保”暖心回归! 保障升级价格不变,医保个账可为全家缴费!

11月22日&#xff0c;2024年“云浮云福保”项目启动会在广东省云浮市迎宾馆成功举办。记者在会上获悉&#xff0c;“云浮云福保”是在云浮市医疗保障局、云浮市金融工作局、国家金融监督管理总局云浮监管分局指导下&#xff0c;的指导下&#xff0c;由中国人民财产保险股份有限…

网络安全之渗透测试入门准备

渗透测试入门所需知识 操作系统基础&#xff1a;Windows&#xff0c;Linux 网络基础&#xff1a;基础协议与简单原理 编程语言&#xff1a;PHP&#xff0c;python web安全基础 渗透测试入门 渗透测试学习&#xff1a; 1.工具环境准备&#xff1a;①VMware安装及使用&#xff1b…

Modbus转Profinet网关连接PLC与天信流量计通讯案例

本文将为您详细介绍如何成功连接PLC与天信流量计&#xff1a;从选择合适的Modbus转Profinet网关开始&#xff0c;到设置网关以实现通讯连接&#xff0c;还会涵盖部署和故障排除过程中可能遇到的一些问题。 首先&#xff0c;选择合适的Modbus转Profinet网关至关重要。我们选用基…

竞赛python区块链实现 - proof of work工作量证明共识算法

文章目录 0 前言1 区块链基础1.1 比特币内部结构1.2 实现的区块链数据结构1.3 注意点1.4 区块链的核心-工作量证明算法1.4.1 拜占庭将军问题1.4.2 解决办法1.4.3 代码实现 2 快速实现一个区块链2.1 什么是区块链2.2 一个完整的快包含什么2.3 什么是挖矿2.4 工作量证明算法&…

微信小游戏上线流程

微信小游戏上线是一个需要经过一系列步骤的过程。以下是一个一般性的微信小游戏上线流程&#xff0c;请注意&#xff0c;上述步骤可能会有微信平台的政策和规定的变化&#xff0c;因此建议在开发过程中及时查阅微信小游戏的官方文档和最新政策。北京木奇移动技术有限公司&#…

前后端分离SpringBoot+vue的买菜农副产品多功能商城

1&#xff0c;项目介绍 本系统主要针对买菜而设计&#xff0c;其功能有菜品基本信息管理、商品类别管理、系统订单管理、评论管理、系统用户管理等功能模块。并且本系统采用了现在流行的SpringBootVue进行的设计与实现&#xff0c;其中Tomcat为服务器&#xff0c;MySQL为数据库…

SpringBoot定时任务(一看就会)

一、引入依赖 定时任务是spring boot框架提供的基础能力之一&#xff0c;所以其依赖是在spring-boot-starter里面&#xff0c;但是一般开发的时候我们直接引入web依赖即可&#xff0c;web依赖中包含了spring-boot-starter。要注意的是Spring Boot 从版本1.3.0开始提供对定时任务…

写单元测试,没你想得那么简单!

前言 单元测试是什么我们就简单介绍一下&#xff1a; 单元测试是针对程序模块&#xff08;软件设计的最小单位&#xff09;来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。 接下来是本人对单元测试的理解和实践。里面没有废话&#xff0c;希望每句话能说到你心…

LangChain: 类似 Flask/FastAPI 之于 Django,LangServe 就是「LangChain 自己的 FastAPI」

原文&#xff1a;LangChain: 类似 Flask/FastAPI 之于 Django&#xff0c;LangServe 就是「LangChain 自己的 FastAPI」 - 知乎 说明&#xff1a;LangServe代替 langchainserver 成为新的langchain 部署工具 官网资料&#xff1a;&#x1f99c;️&#x1f3d3; LangServe | &…

批量插入SQL 错误 [933] [42000]: ORA-00933: SQL 命令未正确结束

使用DBeaver向【oracle数据库】插入大量数据 INSERT INTO Student(name,sex,age,address,birthday) VALUES(Nike,男,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike…

【JDK源码阅读】什么是 avoid getfield opcode ?

说明&#xff1a;JDK源码版本为 Oracle JDK 8 1. 背景 阅读 java.lang.String 的源码&#xff0c;会发现有些地方注释为/* avoid getfield opcode */&#xff0c;此处的代码是将当前类定义的成员变量引用为本地变量&#xff0c;从字面意思理解&#xff0c;是为了避免使用 get…

计算机基础知识——字,字节,进制,short,byte等

目录 进制位&#xff0c;字节&#xff0c;字Byte&#xff0c;ShortByteBuf有符号数和无符号数 进制 HEX&#xff0c;Hexadecimal &#xff0c;十六进制。 DEC&#xff0c;Decimal &#xff0c;十进制。 OCT&#xff0c;Octal &#xff0c;八进制。 BIN&#xff0c;Binary &a…