【多线程】ThreadLocal是什么?有哪些使用场景?使用ThreadLocal需要注意些什么?

news2025/1/21 2:54:00

文章目录

  • 前言
  • 一、ThreadLocal 是什么?
  • 二、有哪些使用场景?
  • 三、实现原理
  • 四、在线程池中使用 ThreadLocal 为什么可能导致内存泄露呢?
  • 五、线程池中,如何正确使用 ThreadLocal?
  • 六、ThreadLocal 核心方法

前言

一、ThreadLocal 是什么?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。

二、有哪些使用场景?

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session管理等问题。

ThreadLocal 使用例子:

public class TestThreadLocal {
	
	//线程本地存储变量
	private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return 0;
		}
	};
 
	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {//启动三个线程
			Thread t = new Thread() {
				@Override
				public void run() {
					add10ByThreadLocal();
				}
			};
			t.start();
		}
	}
	
	/**
	 * 线程本地存储变量加 5
	 */
	private static void add10ByThreadLocal() {
		for (int i = 0; i < 5; i++) {
			Integer n = THREAD_LOCAL_NUM.get();
			n += 1;
			THREAD_LOCAL_NUM.set(n);
			System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
		}
	}
	
}

打印结果:启动了 3 个线程,每个线程最后都打印到 “ThreadLocal num=5”,而不是 num 一直在累加直到值等于 15

Thread-0 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-0 : ThreadLocal num=2
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-2 : ThreadLocal num=1
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=2
Thread-1 : ThreadLocal num=3
Thread-1 : ThreadLocal num=4
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=5
Thread-2 : ThreadLocal num=4
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=5

三、实现原理

按照我们第一直觉,感觉 ThreadLocal 内部肯定是有个 Map 结构,key 存了 Thread,value 存了 本地变量 V 的值。每次通过 ThreadLocal 对象的 get() 和 set(T value) 方法获取当前线程里存的本地变量、设置当前线程里的本地变量。

在这里插入图片描述

而 JDK 的实现里面这个 Map 是属于 Thread,而非属于 ThreadLocal。ThreadLocal 仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在 Thread 里面。ThreadLocalMap 属于 Thread 也更加合理。

还有一个更加深层次的原因,这样设计不容易产生内存泄露。
ThreadLocal 持有的 Map 会持有 Thread 对象的引用,只要 ThreadLocal 对象存在,那么 Map 中的 Thread 对象就永远不会被回收。ThreadLocal 的生命周期往往比线程要长,所以这种设计方案很容易导致内存泄露。

JDK 的实现中 Thread 持有 ThreadLocalMap,而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用(WeakReference),所以只要 Thread 对象可以被回收,那么 ThreadLocalMap 就能被回收。JDK 的这种实现方案复杂但更安全。

在这里插入图片描述

四、在线程池中使用 ThreadLocal 为什么可能导致内存泄露呢?

在线程池中线程的存活时间太长,往往都是和程序同生共死的,这样 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。
Entry 中的 Value 是被 Entry 强引用的,即便 value 的生命周期结束了,value 也是无法被回收的,导致内存泄露。

五、线程池中,如何正确使用 ThreadLocal?

在 finally 代码块中手动清理 ThreadLocal 中的 value,调用 ThreadLocal 的 remove()方法。

六、ThreadLocal 核心方法

  • 设置 Thread 对应的 Value 值,首次会创建一个 ThreadLocalMap ,添加 ThreadLocal - Value 到 ThreadLocalMap 中,并且绑定 ThreadLocalMap 到当前线程。
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,绑定到当前线程。
void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 
  • 通过 ThreadLocalMap 获取当前线程的存储的 Value 值
public T get() {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}
 
  • 设置 ThreadLocal 的初始化值,未 set(T value) 初次获取 Thread 对应的 Value 值时会调用,即被 setInitialValue 方法调用。需要重写该方法。
protected T initialValue() {
	return null;
}
  • 移除当前线程存储的 Value 值。当 ThreadLocal 不在使用,最好在 finally 语句块中,调用 remove() 方法,释放去 Value 的引用,避免内存泄露。
public void remove() {
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
		m.remove(this);
}

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

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

相关文章

韶音的骨传导耳机怎么样,韶音骨传导耳机值得入手吗

常常有人在问韶音的骨传导耳机怎么样以及韶音骨传导耳机值得入手吗这类问题&#xff0c;其实韶音的骨传导耳机在质量方面还是不错的&#xff0c;而且实力上在骨传导中也有着一定的底蕴&#xff0c;具备了多种功能&#xff0c;作为国产品牌的骨传导耳机&#xff0c;在国际市场中…

vue项目启动npm run ‘配置‘(读取的配置信息详情)

1&#xff1a; VSCode终端启动命令 1-1&#xff1a; npm run serve&#xff0c;配置serve默认就是读取.env.development

SpringBoot中使用EMQX实现MQTT通讯

简述 之前写过一篇SpringBoot通过Netty实现TCP服务的文章&#xff0c;本篇与之前那篇实现的场景类似&#xff0c;都是服务器与客户端之间双向交互&#xff0c;但个人觉得MQTT的方式实现更好&#xff0c;优雅。 基础 MQTT协议是通过MQTT服务器转发消息&#xff0c;MQTT服务器…

C++动态内存管理+模板

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

Emgu调用摄像头

1&#xff0c;安装EMgu 2,调用摄像头 public FaceLoad(){InitializeComponent();try{capture new Capture();capture.Start();//摄像头开始工作capture.ImageGrabbed frameProcess;//实时获取图像}catch (NullReferenceException excpt){//MessageBox.Show(excpt.Message);}}…

数据结构算法刷题:背包问题

整数和是p&#xff0c;负数和是s-p&#xff0c;那么target p - (s-p)&#xff0c;求出p (st)//2 class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: target sum(nums) if target < 0 or target % 2: #target 一定是偶数而且是大于…

界面控件DevExpress WinForms工具栏菜单组件,模拟流行办公软件!

DevExpress WinForms的工具栏和菜单组件灵感来自于Microsoft Office&#xff0c;并针对WinForms开发人员进行了优化&#xff0c;可以帮助开发者快速模拟当下流行的办公软件应用程序。 DevExpress WinForms有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业…

《向量数据库指南》——向量数据库Milvus Cloud 2.3的可运维性:从理论到实践

一、引言 在数据科学的大家庭中,向量数据库扮演着重要角色。它们通过独特的向量运算机制,为复杂的机器学习任务提供了高效的数据处理能力。然而,如何让这些数据库在生产环境中稳定运行,成为了运维团队的重要挑战。本文将深入探讨向量数据库的可运维性,并分享一些有趣的案…

基于STM32设计的格力空调遥控器

一、格力空调协议介绍 格力空调的红外控制协议被称为格力红外通讯协议或者格力红外遥控协议。这个协议定义了一系列红外信号&#xff0c;可以用来控制格力空调的各种操作&#xff0c;例如开关、温度控制、模式选择、风速控制等等。 格力空调的红外控制协议是一种自定义协议&a…

进程基本概念

一、什么是进程&#xff08;任务&#xff09; 进程&#xff1a;一个被加载到内存中的程序/正在运行中的程序。 开机时&#xff0c;先将操作系统加载到内存中。 ps -ajx 查询运行中的进程 二、操作系统如何管理进程&#xff1f; 前提&#xff1a;如何利用属性认识事…

使用 crontab 定时任务使用 curl 发送请求

crontab 简单用法 crontab 一般是 linux 系统自带的 输入以下命令可以添加定时任务&#xff0c;里面有 crontab 的说明及示例 crontab -e示例格式如下 # 前面五个分别代表分、时、天、月、周&#xff0c;后面就是命令 * * * * * command例如 * * * * * command就是每分钟执行…

图的应用(最小生成树,最短路径,有向无环图)

目录 一.最小生成树 1.生成树 2.无向图的生成树 3.最小生成树算法 二.最短路径 1.单源最短路径---Dijkstra&#xff08;迪杰斯特拉&#xff09;算法 2.所有顶点间的最短路径---Floyd&#xff08;弗洛伊德&#xff09;算法 三.有向无环图的应用 1.AOV网&#xff08;拓扑…

国内CRM软件系统厂商排名

我们知道CRM软件成为了企业管理中不可或缺的一部分&#xff0c;目前国内CRM厂商排名是怎样的呢&#xff1f;经过评估名列前茅的分别是Zoho CRM、Salesforce CRM、Microsoft Dynamics 、SAP CRM、HubSpot CRM。 1.Zoho Zoho CRM凭借先进的技术和创新的解决方案&#xff0c;帮…

2023年母婴亲子产业研究报告

第一章 行业发展概况 母婴亲子领域是一个综合性的产业&#xff0c;主要聚焦于为孕产妇、婴幼儿及家庭提供全方位的服务和产品。该产业致力于为孕产妇和家庭在孕育、育儿及亲子时期提供必要的支持和便捷。其核心业务涉及婴幼儿商品、孕产妇健康、亲子教育、家庭旅行体验以及亲子…

递归算法学习——图像渲染,岛屿的数量,最大的岛屿

目录 ​编辑 一&#xff0c;图像渲染 1.题意 2.解释 3.题目接口 4.解题思路及代码 二&#xff0c;岛屿的数量 1.题意 2.解释 3.题目接口 4.解题思路及代码 三&#xff0c;最大的岛屿 1.题意 2.解释 3.题目接口 4.解题代码即思路 一&#xff0c;图像渲染 1.题意…

MySQL——笔试测试题

解析&#xff1a; 要查询各科目的最大分数&#xff0c;可以使用如下的SQL语句&#xff1a; SELECT coursename, MAX(score) FROM t_stuscore GROUP BY coursename; 这条SQL语句使用了MAX()聚合函数来获取每个科目的最大分数&#xff0c;并使用GROUP BY子句按照科目进行分组…

核货宝:收银系统后台一般是怎样的,有哪些功能

收银系统后台是一个重要的管理工具&#xff0c;它为企业提供了对收银机的全面控制和配置。收银系统后台是一个用于管理和配置收银机的软件界面。它通常由以下几个主要部分组成&#xff1a; 1. 登录和权限管理 收银系统后台需要一个安全的登录系统&#xff0c;以确保只有授权人…

Mojo 语言官网

Mojo面向 AI 开发者的新型编程语言&#xff0c;无缝支持CPU、GPU&#xff0c;兼容Python&#xff0c;跟Python类似的语法&#xff0c;但是比Python快68000倍。目前Mojo仅支持Ubuntu&#xff0c;暂不支持Windows和Mac&#xff0c;可以在Mojo Playground先体验一下。 Mojo 语言…

ostringstream 多线程下性能问题探究

文章目录 背景火焰图ostringstream 的结构引用 背景 在实习过程中&#xff0c;有一个业务场景需要用到 ostringstream&#xff0c;但经过导师提醒&#xff0c;ostringstream 在多线程关系下&#xff0c;竞态消耗较大&#xff0c;但对于当前业务场景&#xff0c;每次操作&#…

【美团3.18校招真题2】

大厂笔试真题网址&#xff1a;https://codefun2000.com/ 塔子哥刷题网站博客&#xff1a;https://blog.codefun2000.com/ 最多修改两个字符&#xff0c;生成字典序最小的回文串 提交网址&#xff1a;https://codefun2000.com/p/P1089 由于字符串经过修改一定为回文串&#x…