ThreadLocal 原理及源码详解

news2024/11/27 6:20:42

什么是ThreadLocal?

ThreadLocal 是一种提供线程本地变量(也称为线程局部变量)的类,这种变量确保了在不同的线程中访问同一个 ThreadLocal 变量时,每个线程会有一个该变量的私有副本,即使多个线程修改了相同的 ThreadLocal 变量,每个线程所访问的副本仍然是独立的,从而避免了多线程环境下共享变量可能导致的数据不一致和竞争问题。

ThreadLocal 使用:

ThreadLocal 常用方法:

  • ThreadLocal #ThreadLocal():ThreadLocal 构造方法。
  • ThreadLocal #set(T value):设置当前线程绑定的局部变量。
  • ThreadLocal #get():获取当前线程绑定的局部变量。
  • ThreadLocal #remove():移出当前线程绑定的局部变量,防止内存泄漏。
  • ThreadLocal #initialValue():返回当前线程局部变量的初始值。

ThreadLocal 源码分析

ThreadLocal 内部结构:

  • 我们知道 ThreadLocal 是线程本地变量,那 ThreadLocal 肯定是线程的一个属性。
  • ThredLocal 类的内部有一个 ThreadLocalMap 的内部类,来帮我们进行的数据的存储。
  • ThreadLocalMap 的 key 是 ThreadLocal,value 是我们要操作的 value 值,我们可以把 ThreadLocalMap 理解为操作 ThreadLocal 的工具类。

在这里插入图片描述

我们知道 ThreadLocal 是属于 Thread 的,以下代码是摘自 Thread 源码中的一段:

class Thread implements Runnable {

    //与线程相关的 ThreadLocal值,此Map由 ThreadLocal 类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与线程相关的InheritableThreadLocal 值。此Map 由 InheritableThreadLocal 类维护(子线程相关的 本次不讨论) 
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

可以看到我们本篇讨论的 ThreadLocal 出现了,其出现的地方是 Thread 类中,是 Thread 类的一个变量,ThreadLocal 出现的同时,我们发现了 ThreadLocalMap 的存在,根据 ThreadLocal.ThreadLocalMap 我们知道 ThreadLocalMap 是 ThreadLocal 的一个内部类,我们接着追踪一下 ThreadLocal 源码。

ThreadLocalMap#set(T value) 方法源码分析:

//往ThreadLocalMap 中设置 ThreadLocal 和 value 的关系
private void set(ThreadLocal<?> key, Object value) {
	//Entry 数组
	Entry[] tab = table;
	//获取数组的长度
	int len = tab.length;
	//返回下一个hashcode
	int i = key.threadLocalHashCode & (len-1);

	//遍历数组
	for (Entry e = tab[i];
		 e != null;
		 e = tab[i = nextIndex(i, len)]) {
		//获取key ThreadLocal
		ThreadLocal<?> k = e.get();
		//判断是否是当前key
		if (k == key) {
			//替换 value  返回
			e.value = value;
			return;
		}
		//key 为空
		if (k == null) {
			//key 为null 时候 清理过期的 entry 有助于 JVM GC
			replaceStaleEntry(key, value, i);
			return;
		}
	}
	
	//创建一个 entry 节点
	tab[i] = new Entry(key, value);
	//size 加1
	int sz = ++size;
	//是否大于等于负载因子 大于等于则需要扩容
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
		//扩容
		rehash();
}



ThreadLocalMap#replaceStaleEntry 方法源码分析:

//把key value 设置到 staleSlot位置上 这里的key 为null
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
							   int staleSlot) {
	//获取当前	Entry 数组						   
	Entry[] tab = table;
	//数组长度
	int len = tab.length;
	//entry
	Entry e;


	int slotToExpunge = staleSlot;
	//从staleSlot 往前找entry 直到 (e = tab[i]) == null 结束
	for (int i = prevIndex(staleSlot, len);
		 (e = tab[i]) != null;
		 i = prevIndex(i, len))
		 //key为空 找到了需要废弃的位置了
		if (e.get() == null)
			slotToExpunge = i;

	从staleSlot 往后找entry 直到 (e = tab[i]) == null 结束
	for (int i = nextIndex(staleSlot, len);
		 (e = tab[i]) != null;
		 i = nextIndex(i, len)) {
		//(e = tab[i]) != null
		//获取key 
		ThreadLocal<?> k = e.get();

		//找到了自己 e 就是自己 e 的key 为 null  value 不为 null 
		if (k == key) {
			//替换 value 
			e.value = value;
			//交换 staleSlot 和 i 位置的 entry i 位置的entry 是 e 也是脏 entry 也是传过来的entry
			tab[i] = tab[staleSlot];
			//把当 entry e 也就是当前传过来的无效的脏的 entry的 key value 替换到 staleSlot 位置上 i 位置现在是个脏的无效的 entry
			tab[staleSlot] = e;
			
			if (slotToExpunge == staleSlot)
				//等于 表示 之前没有被改变过 也就是往前找 没有找到 方法入参 key 对应的entry 
				//因为当前遍历是从 staleSlot向后遍历 因此 i 大于 staleSlot   i 位置现在是个脏的无效的 entry 需求被清理掉 
				//赋值为 i 从i 的位置开始清理无效的脏的entry
				slotToExpunge = i;
			//遍历清除 脏的 也就是无效的 entry  expungeStaleEntry 方法 从 i 开始往后清理 无效的脏的 entry 
			cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
			return;
		}

		//key == null 表示找到了无效的数据 
		//如果此时 slotToExpunge 还是等于 staleSlot 说明在向前的循环中没有出现过期数据
		//i 赋值给 slotToExpunge
		if (k == null && slotToExpunge == staleSlot)
			slotToExpunge = i;
	}

	//走到这里表示没有清理无效的 脏的entry 设置 就直接设置  staleSlot 位置value 为空
	tab[staleSlot].value = null;
	//然后直接在把无效数据设置到 staleSlot 位置上 等待清理
	tab[staleSlot] = new Entry(key, value);
	
	//如果slotToExpunge == staleSlot 表示除了 staleSlot 位置上是过期数据之外 向前向后都没有找到过期数据
	//slotToExpunge 不等于 staleSlot 表示还有无效数据要清理
	if (slotToExpunge != staleSlot)
		cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

ThreadLocalMap#expungeStaleEntry方法源码分析:

//从staleSlot位置开始往后找entry 遇到entry key为null 的设置其value 也为null 直到遇到entry 为null 结束 
private int expungeStaleEntry(int staleSlot) {
	//获取entry 数组
	Entry[] tab = table;
	//获取数组的长度
	int len = tab.length;

	// expunge entry at staleSlot
	//设置 staleSlot 位置的entry key 和value 都为 null 方便 JVM GC
	tab[staleSlot].value = null;
	tab[staleSlot] = null;
	//entry 数组长度-1
	size--;

	// Rehash until we encounter null
	Entry e;
	int i;
	//rehash 操作  (e = tab[i]) != null 才会继续循环 也就是 (e = tab[i]) == null 结束
	for (i = nextIndex(staleSlot, len);
		 (e = tab[i]) != null;
		 i = nextIndex(i, len)) {
		//获取entry 的key 也就是 ThreadLocal
		ThreadLocal<?> k = e.get();
		if (k == null) {
			//key 为 null 设置 value 也为 null
			e.value = null;
			tab[i] = null;
			//entry 数组长度-1
			size--;
		} else {
			//获取key 在数组中的位置
			int h = k.threadLocalHashCode & (len - 1);
			if (h != i) {
				//不是当前循环的位置 把当前位置设置为空
				tab[i] = null;
				
				// Unlike Knuth 6.4 Algorithm R, we must scan until
				// null because multiple entries could have been stale.
				//key 所在的位置不为空
				while (tab[h] != null)
					//递增找下一个为空的位置
					h = nextIndex(h, len);
				//为空  设置为 e
				tab[h] = e;
			}
		}
	}
	return i;
}

ThreadLocal #get() 源码分析:

get 方法逻辑很简单,就是获取当前线程绑定的局部变量。

//获取当前线程的局部变量
public T get() {
	//获取当前线程
	Thread t = Thread.currentThread();
	//获取当前线程的 ThreadLocalMap
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		//map 不为空 获取当前 ThreadLocal 对应的 entry
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			//获取value
			T result = (T)e.value;
			return result;
		}
	}
	//map 为空 创建一个map 设置初始值为 null 就是初始化操作
	return setInitialValue();
}

ThreadLocal #remove() 源码分析:

remove 方法就是移出当前线程绑定的局部变量,防止内存泄漏。

//删除当前线程的局部变量
//删除当前线程的局部变量
public void remove() {
	 //获取当前线程的 ThreadLocalMap
	 ThreadLocalMap m = getMap(Thread.currentThread());
	 if (m != null)
		 //不为空 执行删除操作
		 m.remove(this);
}

//根据 key 删除 entry
private void remove(ThreadLocal<?> key) {
	//entry 数组
	Entry[] tab = table;
	//数组的长度
	int len = tab.length;
	//找到 key 在entry 数组中的位置
	int i = key.threadLocalHashCode & (len-1);
	for (Entry e = tab[i];
		 e != null;
		 e = tab[i = nextIndex(i, len)]) {
		if (e.get() == key) {
			//找到key 删除掉 clear 方法其实就是赋值为 null 方便 JVM 回收
			e.clear();
			//清理无效的 entry 上面set 方法中有分析
			expungeStaleEntry(i);
			return;
		}
	}
}

ThreadLocal #cleanSomeSlots() 源码分析:

//清除无效数据
private boolean cleanSomeSlots(int i, int n) {
	boolean removed = false;
	Entry[] tab = table;
	int len = tab.length;
	do {
		//遍历数据
		i = nextIndex(i, len);
		Entry e = tab[i];
		//是否是过期数据
		if (e != null && e.get() == null) {
			n = len;
			removed = true;
			//清理过期数据
			i = expungeStaleEntry(i);
		}
	} while ( (n >>>= 1) != 0);
	return removed;
}

关于 ThreadLocal 的源码解析就分享到这里,本篇主要是对 ThreadLocal 的源码进行了分析,后面会接着分享 ThreadLocal 的使用及常用问题,希望可以帮助到有需要的伙伴。

欢迎提出建议及对错误的地方指出纠正。

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

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

相关文章

MATLAB环境下基于同步压缩变换重分配算子的瞬时频率估计

瞬时频率是表征非平稳信号特征的重要物理量&#xff0c;已经被广泛应用于桥梁振动检测、地震勘测、机械、电力系统、雷达、通信、医学等各个方面。瞬时频率的概念最早由Carson提出&#xff0c;后来&#xff0c;Gabor提出解析信号的定义&#xff0c;为瞬时频率的研究提供了新的方…

「不羁联盟/XDefiant」4月20号开启服务器测试,游戏预下载安装教程

XDefiant》开启Alpha测试&#xff0c;这是一款免费游玩的快节奏 FPS 竞技游戏&#xff0c;可选择特色阵营&#xff0c;搭配个性化的装备&#xff0c;体验 6v6 对抗或是线性游戏模式。高品质射击竞技端游XDefiant以6v6双边对抗为核心&#xff0c;对局模式分为区域与线性两大类&a…

【Spring】-编程式事务和声明式事务

spring中控制事务的方式有两种&#xff1a;编程式事务和声明式事务&#xff0c;今天我以两种事务出发&#xff0c;对spring中实现事务的EnableTransactionManagement和Transaction两个注解的底层原理进行讨论。 一、编程式事务 什么是编程式事务&#xff1f; 硬编码的方式实现…

数据结构-栈和队列刷题集(长期更新)

文章目录 万能计算器的实现以及源码分析1. leetcode 150 逆波兰表达式求值 万能计算器的实现以及源码分析 /*** 我们尝试写一个完整版的计算器,由于计算机不能很好的识别括号,所以一般要转换为逆波兰表达式求解* 思路解析 :* 1. 输入一个 中缀表达式* 2. 中缀表达式转化为list…

鸡汤笔记-致自己

《你只是看起来很努力》李尚龙 我们看起来每天熬夜&#xff0c;却只是拿着手机点了无数个赞&#xff1b;看起来在图书馆坐了一天&#xff0c;却真的只是坐了一天&#xff1b;看起来买了很多书&#xff0c;只不过晒了个朋友圈&#xff1b;看起来每天很晚地离开办公室&am…

聊聊go语言中的内存填充

写在文章开头 我们都知道数据加载到CPU缓存中可以提升执行性能&#xff0c;所以为了保证每一个结构体中的成员能够完整的被单个CPU核心加载以避免缓存一致性问题而提出内存对齐&#xff0c;这篇文章笔者会从go语言的角度来讨论这个优化机制。 Hi&#xff0c;我是 sharkChili &…

基于Springboot+Vue的Java项目-网上点餐系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

杀鸡焉用牛刀,用unity3D开发数字孪生是大材小用吗?

"杀鸡焉用牛刀"这句话的意思是指使用过于强大或不适合的工具来完成一个简单的任务。而用Unity3D开发数字孪生并不一定是大材小用。 Unity3D是一款功能强大的游戏开发引擎&#xff0c;它可以用于开发各种类型的游戏和交互应用程序。数字孪生是一种基于现实世界对象的虚…

网络靶场实战-PE 自注入

默认的 Windows API 函数&#xff08;LoadLibrary、LoadLibraryEx&#xff09;只能加载文件系统中的外部库&#xff0c;无法直接从内存中加载 DLL&#xff0c;并且无法正确地加载 EXE。有时候&#xff0c;确实需要这种功能&#xff08;例如&#xff0c;不想分发大量文件或者想增…

Redis入门到通关之解决Redis缓存一致性问题

文章目录 ☃️概述☃️数据库和缓存不一致采用什么方案☃️代码实现☃️其他 ☃️概述 由于我们的 缓存的数据源来自于数据库, 而数据库的 数据是会发生变化的, 因此,如果当数据库中 数据发生变化,而缓存却没有同步, 此时就会有 一致性问题存在, 其后果是: 用户使用缓存中的过…

Python 数据结构和算法实用指南(二)

原文&#xff1a;zh.annas-archive.org/md5/66ae3d5970b9b38c5ad770b42fec806d 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第四章&#xff1a;列表和指针结构 我们已经在 Python 中讨论了列表&#xff0c;它们方便而强大。通常情况下&#xff0c;我们使用 Python…

【C语言__基础概念__复习篇8】

目录 前言 一、C语言是什么 二、C语言的发展历史 三、编译器的选择 3.1 编译和链接 3.2 编译器的对比 3.3 VS如何使用 四、main函数 五、关键字 六、字符和ASCII编码 七、字符串和\0 八、转义字符 九、注释 十、数据类型 10.1 数据类型的介绍 10.2 数据类型大小的计…

有哪些网站设计教程

网站设计教程是帮助人们学习如何设计和开发网站的资源&#xff0c;它们提供了从基础知识到高级技巧的全方位指导。无论您是初学者还是经验丰富的开发者&#xff0c;都可以从这些教程中获益。下面是一些广受欢迎的网站设计教程&#xff0c;它们涵盖了各种技术和工具&#xff1a;…

Linux之进程控制进程终止进程等待进程的程序替换替换函数实现简易shell

文章目录 一、进程创建1.1 fork的使用 二、进程终止2.1 终止是在做什么&#xff1f;2.2 终止的3种情况&&退出码的理解2.3 进程常见退出方法 三、进程等待3.1 为什么要进行进程等待&#xff1f;3.2 取子进程退出信息status3.3 宏WIFEXITED和WEXITSTATUS&#xff08;获取…

【C++题解】1345. 玫瑰花圃

问题&#xff1a;1345. 玫瑰花圃 类型&#xff1a;基本运算、小数运算 题目描述&#xff1a; 有一块nn&#xff08;n≥5&#xff0c;且 n 是奇数&#xff09;的红玫瑰花圃&#xff0c;由 nn 个小正方形花圃组成&#xff0c;现要求在花圃中最中间的一行、最中间的一列以及 4 个…

SpringBoot多数据源基于mybatis插件(三)

SpringBoot多数据源基于mybatis插件&#xff08;三&#xff09; 1.主要思路2.具体实现 1.主要思路 MyBatis的插件机制允许你在MyBatis的四大对象&#xff08;Executor、StatementHandler、ParameterHandler和ResultSetHandler&#xff09;的方法执行前后进行拦截&#xff0c;并…

考察自动化立体库应注意的几点

导语 大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;老K。专注分享智能仓储物流技术、智能制造等内容。 整版PPT和更多学习资料&#xff0c;请球友到知识星球 【智能仓储物流技术研习社】自行下载 考察自动化立体仓库的关键因素&#xff1a; 仓库容量&#x…

linux 中ifconfig 无法使用

1、先看问题 2、搜索 ifconfig 命令&#xff0c;看下该命令在哪 yum search ifconfig 可以看到ifconfig命令在 net-tools.x86_64这个包里。 3、下面开始安装&#xff0c;执行下面的命令 yum install net-tools.x86_64 4、查看是否安装成功 ifconfig 看到上面的ip就说明可以用了…

光网络中的低偏SOA与无源波导单片集成

----翻译自Aref Rasoulzadeh Zali等人2021年撰写的文章 摘要 在光通信系统中&#xff0c;非常需要可以通过简单工艺与无源光路单片集成的低偏振相关半导体光放大器&#xff08;SOA&#xff09;。然而&#xff0c;尽管已经报道了几种SOA&#xff0c;但在InP平台中将偏振无关的体…

ROS学习笔记(12)AEB和TTC的实现

0.前提 在自动驾驶领域有许多关于驾驶安全的措施AEB和TTC就是为了驾驶安全而设计出来的。在这篇文章中我会讲解我对AEB和TTC算法的一些理解。本期ROS学习笔记同时也是ros竞速小车的学习笔记&#xff0c;我会将我的部分代码拿出来进行讲解&#xff0c;让大家更好的理解ttc和aeb…