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

news/2024/5/20 6:51:07 标签: 并发编程, 多线程, 线程安全, ThreadLocal, JUC

📫作者简介:小明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%E6%98%AF%E4%BB%80%E4%B9%88-toc" style="margin-left:0px;">一、ThreadLocal是什么

ThreadLocal%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-toc" style="margin-left:0px;">二、ThreadLocal的数据结构

ThreadLocal%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%C2%A0-toc" style="margin-left:0px;">三、ThreadLocal源码解析 

ThreadLocal%E7%9A%84set()%E6%96%B9%E6%B3%95-toc" style="margin-left:40px;">1、 ThreadLocal的set()方法

ThreadLocal%E7%9A%84get()%E6%96%B9%E6%B3%95-toc" style="margin-left:40px;">2、 ThreadLocal的get()方法

ThreadLocal%E7%9A%84remove()%E6%96%B9%E6%B3%95-toc" style="margin-left:40px;">3、 ThreadLocal的remove()方法

ThreadLocal%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF-toc" style="margin-left:0px;">四、ThreadLocal使用场景

ThreadLocal%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E5%8E%9F%E5%9B%A0-toc" style="margin-left:0px;">五、ThreadLocal内存泄露原因

ThreadLocal-toc" style="margin-left:0px;">六、如何正确的使用ThreadLocal

ThreadLocal%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%B0%86key%E8%AE%BE%E7%BD%AE%E4%B8%BA%E5%BC%BA%E5%BC%95%E7%94%A8-toc" style="margin-left:0px;">七、ThreadLocal为什么不将key设置为强引用

总结


本文导读

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

ThreadLocal%E6%98%AF%E4%BB%80%E4%B9%88">一、ThreadLocal是什么

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

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

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

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

ThreadLocal%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84">二、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%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%C2%A0">三、ThreadLocal源码解析 

ThreadLocal%E7%9A%84set()%E6%96%B9%E6%B3%95">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;
		}
	}
}

ThreadLocal%E7%9A%84get()%E6%96%B9%E6%B3%95">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();
}

ThreadLocal%E7%9A%84remove()%E6%96%B9%E6%B3%95">3、 ThreadLocal的remove()方法

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

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

ThreadLocal%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF">四、ThreadLocal使用场景

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

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

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

3、数据跨层传递

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

ThreadLocal%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E5%8E%9F%E5%9B%A0">五、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">六、如何正确的使用ThreadLocal

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

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

ThreadLocal%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%B0%86key%E8%AE%BE%E7%BD%AE%E4%B8%BA%E5%BC%BA%E5%BC%95%E7%94%A8">七、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使用场景,如何正确的使用ThreadLocalThreadLocal内存泄露原因。


http://www.niftyadmin.cn/n/5205251.html

相关文章

负载均衡lvs

简介 ipvsadm 是 Linux 内核中的 IP 虚拟服务器&#xff08;IPVS&#xff09;管理工具。IPVS是 Linux 内核提供的一种负载均衡解决方案&#xff0c;它允许将入站的网络流量分发到多个后端服务器&#xff0c;以实现负载均衡和高可用性。IPVS通过在内核中维护一个虚拟服务器表&a…

vue2.x中使用JS与路由守卫配置、vue2.x中使用TS与路由守卫的配置和vuex的配置以及TS在vue2.x中的使用说明

vue2.x 事件修饰符 <button click.stop.self"handleDelete(item)"></button>在使用 $confirm 时&#xff0c;使用 await 方式时&#xff0c;需要添加 catch 回调&#xff0c;否则无法获取 $confirm 关闭和取消时的标识值&#xff0c;并且后续代码停止执…

【Python】【Torch】神经网络中各层输出的特征图可视化详解和示例

本文对神经网络各层特征图可视化的过程进行运行示例&#xff0c;方便大家使用&#xff0c;有助于更好的理解深度学习的过程&#xff0c;尤其是每层的结果。 神经网络各层特征图可视化的好处和特点如下&#xff1a; 可视化过程可以了解网络对图像像素的权重分布&#xff0c;可…

Axios简单使用与配置安装-Vue

安装Axios npm i axios main.js 导入 import Axios from axios Vue.prototype.$axios Axios简单发送请求 get getTest() {this.$axios({method: GET,url: https://apis.jxcxin.cn/api/title?urlhttps://apis.jxcxin.cn/}).then(res > {//请求成功回调console.log(res)}…

连接k8s和凌鲨

通过连接k8s和凌鲨&#xff0c;可以让研发过程中的重用操作更加方便。 更新容器镜像调整部署规模查看日志运行命令 架构 所有操作通过k8s proxy连接&#xff0c;通过设置namespace label赋予访问权限。只有赋予特定label的namespace才能被访问。 使用步骤 部署k8s proxy 你…

whip和whep

原文为runner365.git大佬的文章 原文链接&#xff1a;https://blog.csdn.net/sweibd/article/details/124552793 WHIP接口 什么是whip 全称: WebRTC-HTTP ingestion protocol (WHIP). rfc地址: rfc-draft-murillo-whip-00 简单说&#xff0c;就是通过HTTP接口能导入webrtc媒…

目标检测YOLO实战应用案例100讲-基于YOLO的小目标检测改进算法

目录 前言 国内外研究现状 常规尺寸目标检测算法 小目标的检测算法

鸿蒙原生应用/元服务开发-AGC分发如何配置版本信息(下)

12.根据《工业和信息化部关于开展移动互联网应用程序备案工作的通知》&#xff0c;自2023年9月初起&#xff0c;在中国大陆地区提供互联网信息服务的APP开发者&#xff0c;需要依法履行APP备案手续&#xff0c;并通过APP分发平台的备案信息核验。 对于2023年9月7日后在AGC新上…