简单了解ThreadLocal

什么是ThreadLocal?

ThreadLocal是线程变量,每个线程可以在一个ThreadLocal里面存放一个变量,这个变量是线程安全的,除了ThreadLocal还可以用栈的本地变量或者锁来保证线程安全,并且可以用于方法间的数据传递。

ThreadLocalMap是ThreadLocal的一个内部类,用于保存数据,key是ThreadLocal的一个弱引用(可以有很多个ThreadLocal),那如果是强引用就不会被回收了,value是存放的变量值

 // 强引用ThreadLocal<Object> threadLocal = new ThreadLocal<>();threadLocal.get();// 弱引用new ThreadLocal<>().get();

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有自己的ThreadLocalMap

简单来说就是每个线程一个ThreadLocalMap,存放很多个 <不同的ThreadLocal,不同的变量值>

应用场景

保存某个信息在线程的任意时刻可以使用,如保存用户user信息,可以获取userId或者其他什么信息

源码解析

hash冲突

和HashMap的链地址法不同,ThreadLocal采用nextIndex()和prevIndex()往前后找位置

nextIndex()和prevIndex()

向后或者向前遍历

 private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}​private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}

set()

  1. 计算下标位置

  2. 下标位置就是空白的,则直接加入

  3. 下标位置有节点,则往后遍历,如果发现key相同的节点,进行值替换

  4. 如果没有过期节点,一直找到空白位置

  5. 如果有过期节点,进行替换,并且进行清理

 public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}​private void set(ThreadLocal<?> key, Object value) {​Entry[] tab = table;int len = tab.length;// 对于同一个线程的下标位置都会相同 int i = key.threadLocalHashCode & (len-1);// 找到合适的位置存放新节点for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();// key相同替换if (k == key) {e.value = value;return;}// 有节点,但是节点的key为null,说明节点被回收了,替换之if (k == null) {replaceStaleEntry(key, value, i);return;}}// 找到了空白的位置,准备加入新节点tab[i] = new Entry(key, value);int sz = ++size;// 超过阈值进行扩容if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}

replaceStaleEntry()

过期节点替换和垃圾清理(往前找到空白前的第一个过期节点作为开始清理点)

slotToExpunge:从这个位置开始垃圾清理

staleSlot:需要替换的过期节点位置

 private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;// 遍历检测还有没有其它过期的元素,直到null,用slotToExpunge记录开始点,之后用于清理数据int slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)// 当前节点过期了,更新slotToExpungeslotToExpunge = i;​// 从staleSlot往后遍历for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();​// 这里主要是为了配合调用者方法,也有一个key相同的处理方式if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;​// 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理if (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}​// 更新slotToExpungeif (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}​// 替换staleSlot位置为新节点,也是这个方法的主要目的tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);​// 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理if (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}

垃圾清理

expungeStaleEntry()

线性清理,遇到空白则停止清理,这里的staleSlot实际上是传进来的slotToExpunge

 
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;​// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;​// Rehash until we encounter nullEntry e;int i;// 往后遍历for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// 直接清理if (k == null) {e.value = null;tab[i] = null;size--;} else {// 进行rehash重新存放位置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.while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}

cleanSomeSlots()

每次循环n/=2,n是table[]数组的长度,直到n==0停止清理,也是线性清理

 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;}

扩容

 // threshold 默认 2/3 * lenif (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();​private void rehash() {// 从开头开始清理所有过期节点,然后移动零散节点到一起expungeStaleEntries();​// 因为有些过期节点被清理,减少扩容的阈值判断if (size >= threshold - threshold / 4)// 2倍扩容,重新rehashresize();}

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

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

相关文章

代码整洁之道【8】-- 单元测试

一、TDD三定律 ①在编写不能通过的单元测试前&#xff0c;不可编写生产代码&#xff1b; ②只可编写刚好无法通过的单元测试&#xff0c;不能编译也算不通过&#xff1b; ③只可编写刚好足以通过当前失败测试的生产代码&#xff1b; 二、保持测试整洁 测试代码和生产代码一样…

sysdba os认证登录和远程登录 AUTHENTICATION_SERVICES 参数

1.在数据库未open状态下&#xff0c;登陆验证的是使用密码文件 验证测试 (通过命令&#xff1a; orapwd fileorapwprod password[密码] forcey&#xff0c;创建密码后 使用sqlplus sys as sysdba 登录时&#xff0c;密码验证的密码文件&#xff0c;不会验证数据库中sys的密…

Jackson 工具类使用及配置指南

前言 Json数据格式这两年发展的很快&#xff0c;其声称相对XML格式有很对好处: 容易阅读&#xff1b; 解析速度快&#xff1b; 占用空间更少。 不过,JSON 和 XML两者纠结谁优谁劣,这里不做讨论,可以参见知乎上为什么XML这么笨重的数据结构仍在广泛应用&#xff1f; 最近在…

kali工具----网络映射器(Network Mapper)

识别活跃的主机 尝试渗透测试之前&#xff0c;必须先识别在这个目标网络内活跃的主机。在一个目标网络内&#xff0c;最简单的方法将是执行ping命令。当然&#xff0c;它可能被一个主机拒绝&#xff0c;也可能被接收。本节将介绍使用Nmap工具识别活跃的主机。 1、网络映射器工具…

【mysql 5.7 没有ini 文件,手动添加配置文件】

在安装目录的根目录添加my.ini配置文件&#xff1a; 注意注释的内容&#xff0c; 其中server-id 在开启日志归档的时候&#xff0c;一定要配置&#xff0c; [mysql] # 设置mysql客户端默认字符集 default-character-setutf8[mysqld] #server id 一定要设置&#xff0c;否则无法…

渗透工具及其知识库(个人笔记)

1.IP搜寻 查看kali网段&#xff1a; ip addr 、 ifconfig namp&#xff1a;nmap -sP xxx.xxx.xxx.0/24 netdiscover&#xff1a;netdiscover xxx.xxx.xxx.0/24 arp&#xff1a;arp-scan -l 2.端口扫描 粗略扫描&#xff1a;nmap <IP> 深度扫描&#xff1a; …

本科生学深度学习一残差网络,解决梯度消失和爆炸

看到订阅的激励还在继续,今天写下残差网络 1、梯度爆炸和梯度消失 梯度爆炸和梯度消失是两种常见的问题,由神经网络的结构和参数初始化方式引起。它们都与深度神经网络中的反向传播过程相关。 梯度爆炸:这是指在反向传播期间,梯度逐渐增大并最终超出了有效范围。这通常发…

深度学习的模型有几类,能干嘛用?

1、基础模型 &#xff08;1&#xff09;卷积神经网络 **卷积&#xff1a;**卷积的本质是通过矩阵运算9的方式将输入数据进行空间上的滤波&#xff0c;有效地提取数据中的局 部特征&#xff0c;从而实现特征数据更高程度的抽象表示。 **池化&#xff1a;**可以理解成“压缩”…

09 Php学习:超级全局变量

超级全局变量 PHP中预定义了几个超级全局变量&#xff08;superglobals&#xff09; &#xff0c;这意味着它们在一个脚本的全部作用域中都可用。 PHP 超级全局变量列表: $GLOBALS$_SERVER$_REQUEST$_POST$_GET$_FILES$_ENV$_COOKIE$_SESSION $GLOBALS $GLOBALS 是 PHP 中的…

实现优雅的并行程序的策略:

前言: 实现优雅且高效的并行程序确实是并行编程中的一大挑战。在设计并行程序时,确保程序的正确性和性能是主要考虑的两个方面。以下是几个关键策略,可帮助在保证程序正确性的前提下优雅地实现并行程序: 实现优雅的并行程序的策略如下:第一、精心设计任务分解: 并行程序…

【MYSQL】字符串存储类型该怎么选:mysql中char、varchar、text的区别

文章目录 一. 简述二. 各自特点 参考官网&#xff1a;mysql-string-type-syntax 一. 简述 char长度固定&#xff1a; 即每条数据占用等长字节空间&#xff1b;适合用在身份证号码、手机号码等定。varchar可变长度&#xff1a;可以设置最大长度&#xff1b;适合用在长度可变的属…

2024-04-09(CSS移动Web+JS进阶)

1.平面转换 作用&#xff1a;为元素增加动态效果&#xff0c;一般和过度配合使用 定义&#xff1a;改变盒子在平面内的形态&#xff08;位移、旋转、缩放、倾斜&#xff09; transfrom属性中有许多方法支持对盒子进行各种变换。 2.空间变换 XYZ三条轴线构成立体空间&#…

Spring源码复习之BeanDefinition理解

文章目录 一. 基本描述二. 源码分析三. BeanDefinition重要实现类1. GenericBeanDefinition2. RootBeanDefinition3. ChildBeanDefinition4. AnnotatedGenericBeanDefinition5. ConfigurationClassBeanDefinition6. ScannedGenericBeanDefinition四. 最佳实践五. 常见问题

算法打卡day39|动态规划篇07| Leetcode 70. 爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数

算法题 Leetcode 70. 爬楼梯&#xff08;进阶版&#xff09; 题目&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整数。 输入描述…

全新4.0版本圈子社交论坛系统 ,可打包小程序,于TP6+uni-app 全开源 可打包小程序app uniapp前端+全开源+独立版

简述 首先 圈子系统的核心是基于共同的兴趣或爱好将用户聚集在一起&#xff0c;这种设计使得用户能够迅速找到与自己有共同话题和兴趣的人。 其次 圈子系统提供了丰富的社交功能&#xff0c;如发帖、建圈子、发活动等&#xff0c;并且支持小程序授权登录、H5和APP等多种形式…

dfs板子

递归实现排列 留着明早省赛之前看 #include<iostream> using namespace std; int arr[10010]; int brr[10010]; int n,k; void dfs(int num){if(num > n){for(int i 1;i < n;i){cout << arr[i] << " ";}cout << endl;return;}for(in…

C# winform实现搜索读取已安装的路径

最近在开发winform窗体应用简单的桌面插件,需求是要点击按钮,就可打开相应程序软件但问题在于每个人电脑上的软件安装路径不同所以涉及到读取路径的的问题. 这位兄台写的播客解决了我的问题,甚是不错. ↓↓↓ 解决方案

二、计算机网络物理层基础知识

一、物理层 物理层接口特性&#xff1a;解决如何在连接各种计算机传输媒体上的传输数据比特流&#xff0c;而不是指具体的传输媒体 物理层的主要任务 &#xff1a;确定与传输媒体接口有关的一些特性>定义标准 1、机械特性&#xff1a;定义物理连接的特性&#xff0c;规定物理…

golang kafka sarama源码分析

一些理论 1.topic支持多分区&#xff0c;每个分区只能被组内的一个消费者消费&#xff0c;一个消费者可能消费多个分区的数据&#xff1b; 2.消费者组重平衡的分区策略&#xff0c;是由消费者自己决定的&#xff0c;具体是从消费者组中选一个作为leader进行分区方案分配&#…

人员抽烟AI检测算法原理介绍及实际场景应用

抽烟检测AI算法是一种基于计算机视觉和深度学习技术的先进工具&#xff0c;旨在准确识别并监测个体是否抽烟。该算法通过训练大量图像数据&#xff0c;使模型能够识别出抽烟行为的关键特征&#xff0c;如烟雾、手部动作和口部形态等。 在原理上&#xff0c;抽烟检测AI算法主要…