ThreadLocal内部结构深度解析(Ⅰ)

目录

使用ThreadLocal

 例子

内部结构分析

源码解析

 图示详解


ThreadLocal是Java中一个非常重要且常用的线程局部变量工具类,它使得每个线程可以独立地持有自己的变量副本,而不是共享变量,解决了多线程环境下变量共享的线程安全问题。下面我将从多个维度深入分析ThreadLocal的内部结构和工作原理。

使用ThreadLocal

// 1. 初始化:创建ThreadLocal变量
private static ThreadLocal<T> threadLocal = new ThreadLocal<>();// 2. 设置值:为当前线程设置值
threadLocal.set(value);  // value为要存储的泛型对象// 3. 获取值:获取当前线程的值
T value = threadLocal.get();  // 返回当前线程存储的值// 4. 移除值:清除当前线程的ThreadLocal变量(防止内存泄漏)
threadLocal.remove();

【注】使用时,通常将ThreadLocal声明为static final以保证全局唯一性

private static ThreadLocal<T> threadLocal = ThreadLocal.withInitial(() -> initialValue);

【注:】 withInitial里面放的是任何能够返回 T 类型实例的 Lambda / Supplier
只要 Supplier 的逻辑最终能 new(或从缓存、工厂、单例池等)拿出一个 T,就合法。

 例子

package com.qcby.test;import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class ThreadLocalTest {private List<String> messages = new ArrayList<>();public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new);public static void add(String message) {holder.get().messages.add(message);}public static List<String> clear() {List<String> messages = holder.get().messages;holder.remove();return messages;}public static void main(String[] args) throws InterruptedException {// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(10);// 提交10个任务for (int i = 0; i < 10; i++) {final int threadId = i;executor.submit(() -> {ThreadLocalTest.add("线程" + threadId + "的消息" );// 打印当前线程的消息System.out.println("线程" + threadId + "的消息列表: " + holder.get().messages);// 清除当前线程的ThreadLocalThreadLocalTest.clear();});}// 关闭线程池executor.shutdown();executor.awaitTermination(1, TimeUnit.SECONDS);// 主线程检查自己的ThreadLocal(应该是空的)System.out.println("主线程的消息列表: " + holder.get().messages);}
}

内部结构分析

根据这里get的源码追溯分析:

追溯到:

源码解析
/*** 获取当前线程的ThreadLocal变量值*/
public T get() {// 1. 获取当前线程对象Thread t = Thread.currentThread();// 2. 获取当前线程的ThreadLocalMap(线程私有数据存储结构)ThreadLocalMap map = getMap(t);// 3. 如果map已存在if (map != null) {// 3.1 以当前ThreadLocal实例为key(也就是代码中的holder),获取对应的EntryThreadLocalMap.Entry e = map.getEntry(this);// 3.2 如果Entry存在if (e != null) {// 3.2.1 强转为泛型类型并返回值@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 4. 如果map不存在或未找到值,初始化并返回默认值return setInitialValue();
}/*** 获取线程的ThreadLocalMap(实际是Thread类的threadLocals字段)*/
ThreadLocalMap getMap(Thread t) {return t.threadLocals; // 直接返回线程对象的成员变量
}/*** 初始化值并存入ThreadLocalMap*/
private T setInitialValue() {// 1. 获取初始值(子类可重写initialValue()方法)T value = initialValue();// 2. 获取当前线程Thread t = Thread.currentThread();// 3. 获取线程的ThreadLocalMapThreadLocalMap map = getMap(t);// 4. 如果map已存在,直接设置值if (map != null) {map.set(this, value);} else {// 5. 如果map不存在,创建新map并存入初始值createMap(t, value);}// 6. 返回初始值return value;
}/*** 创建线程的ThreadLocalMap并存入第一个值*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}/*** 默认初始值实现(可被withInitial覆盖)*/
protected T initialValue() {return null; // 默认返回null
}
 图示详解

所以执行结果:

可以看见一个线程中只有一个信息,而不是它们统一堆砌在一起,原因就是底层是每个线程创建了一个Map对象,每个Map的value就是存入的messages本质是对象,也就是T--ThreadLocalTest对象们,并且它们Map中的Entry中的Key值都是一样的,都是这个ThreadLocal,也就是holder。

注】并不是每个线程的Map只能存放一个value对象,是这里我展示的例子里,一个线程只存了一条,完全可以存入很多条消息,然后add()时就会累加在Map已经创建好的Entry后面也就是:

当然既然是Map,存储Entry就涉及Hash了,这个以后再详谈。

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

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

相关文章

Python 数据挖掘之数据探索

在数据挖掘的流程中&#xff0c;数据探索是非常关键的第一步&#xff0c;它能帮助我们深入了解数据的特点&#xff0c;为后续的预处理和模型构建打下坚实的基础。我们主要围绕四个方面展开&#xff1a;数据对象与特征、数据统计描述、数据可视化以及相关性和相似性度量。一、数…

高并发点赞场景Synchronized、AtomicLong、LongAdder 和 LongAccumulator性能分析

在高并发点赞场景中&#xff0c;我们需要一个高效、线程安全的计数器来记录点赞数。synchronized、AtomicLong、LongAdder 和 LongAccumulator 都是 Java 中用于实现原子操作的类&#xff0c;但它们的性能在高并发下差异显著。性能主要取决于线程竞争程度&#xff1a;竞争越高&…

postgreSQL的sql语句

目录 一&#xff1a;前提准备1.postgreSQL的安装可以参考我下面一片文章&#xff1a; 二&#xff1a;SQL语句 1.相同点&#xff1a;支持标准sql类型 2.参考详细学习地址&#xff1a; 3.postgresql与mysql的不同点 一&#xff1a;前提准备 1.postgreSQL的安装可以参考我下面…

vue3 JavaScript 数据累加 reduce

在Vue 3中&#xff0c;你可以使用JavaScript的reduce方法来处理数据累加。reduce方法通常用在数组上&#xff0c;它将数组中的每个元素通过一个累加器函数&#xff08;accumulator&#xff09;从左到右累积&#xff0c;最终生成一个单一的值。这在计算总和、累加值等场景中非常…

史上最清楚!读者,写者问题(操作系统os)

读者-写者问题是另一个里程碑式的同步互斥问题。它比生产者-消费者更复杂&#xff0c;因为它引入了不对称的访问权限&#xff1a;读者和读者之间是共享的&#xff0c;但写者和任何人&#xff08;包括读者和其他写者&#xff09;之间都是互斥的。我们用一个生动的比喻来解析这个…

使用Starrocks替换Clickhouse的理由

背景 Starrocks和clickhouse都是非常优秀的OLAP数据库&#xff0c;那么什么情况下使用clickhouse&#xff0c;什么场景下使用starrocks呢&#xff0c;本文就简单列举一下他们的优缺点 理由 首先两者都是列存储&#xff0c;并且都实现了列压缩&#xff0c;所以从存储中两者的压缩…

Mybatis 两级缓存可能导致的问题

Mybatis 两级缓存可能导致的问题两级缓存简介一级缓存 localCache效果开关二级缓存两级缓存可能导致的问题分布式环境下查询到过期数据事务隔离级别失效读已提交失效读未提交失效总结两级缓存简介 一级缓存 localCache 效果 一级缓存是 session 或者说事务级别的&#xff0c…

vue3+uniapp 使用vue-plugin-hiprint中实现打印效果

前言&#xff1a; vue3uniapp 使用vue-plugin-hiprint中实现打印效果 官网地址&#xff1a;gitee https://gitee.com/ccsimple/vue-plugin-hiprinthttps://gitee.com/ccsimple/vue-plugin-hiprint 实现效果&#xff1a; 预览打印内容&#xff1a; 实现步骤&#xff1a; 1、安…

【elementUI踩坑记录】解决 el-table 固定列 el-table__fixed 导致部分滚动条无法拖动的问题

目录一、问题背景二、 问题现象三、核心原因四、解决办法增强方案&#x1f680;写在最后一、问题背景 在使用 Element UI 的 el-table 组件时&#xff0c;固定列功能虽然实用&#xff0c;但会引发滚动条交互问题&#xff1a; 固定列区域悬浮显示滚动条但无法正常拖动滚动条 …

【机器人编程基础】python文件的打开和关闭

文件的打开和关闭 在Python中,文件操作是一项基本而重要的任务,涉及到打开、读取、写入、关闭文件等操作。正确地管理文件对于数据持久化、输入输出处理等至关重要。下面将详细解释如何在Python中打开和关闭文件,并提供相应的代码示例。 文件打开 在Python中,可以使用内…

ShenYu实战、问题记录

概述 一款高性能的国产的Apache开源API网关&#xff0c;官方文档。 在ShenYu v2.6.1, ShenYu注册中心只支持http类型&#xff0c;中间件注册类型已经被移除。 所以&#xff0c;请使用http注册类型来注册你的服务。不是微服务注册中心&#xff0c;它只是将元数据、选择器数据、…

走近科学IT版:EasyTire设置了ip,但是一闪之后就变回到原来的dhcp获得的地址

EasyTier 是一款简单、安全、去中心化的内网穿透和异地组网工具&#xff0c;适合远程办公、异地访问、游戏加速等多种场景。无需公网 IP&#xff0c;无需复杂配置&#xff0c;轻松实现不同地点设备间的安全互联。 上次实践的记录&#xff1a;适合远程办公、异地访问的EasyTier…

rk3588平台USB 3.0 -OAK深度相机适配方法

目录 文件更改记录表 1、usb规则添加 2、拉取相关依赖 3、安装python3、安装pip 4、安装依赖 5、安装ffmeg 6、摄像头功能测试 7、将视频拷贝到U盘查看 1、usb规则添加 由于OAK是USB设备,因此为了在使用 udev 工具的系统上与之通信, 您需要添加udev规则以使…

工厂模式总结

工厂模式1. 简单工厂模式&#xff08;Simple Factory&#xff09; 核心思想 定义一个工厂类&#xff0c;根据输入参数创建不同的具体对象。客户端不直接调用具体类的构造函数&#xff0c;而是通过工厂类获取对象。 示例代码 #include <iostream> #include <memory>…

MySQL的三种安装方式(mis、zip、yum)

目录 2.0数据库安装 2.1windows上.mis格式 环境准备 MySQL的安装 环境配置&#xff08;非必要&#xff09; 2.2windows上.zip格式安装 环境准备 配置文件的内容 MySQL的安装 附录可能出现问题 图形工具远程连接数据库 2.3Linux上安装yum包 环境准备 过程命令 My…

串口学习和蓝牙通信HC05(第八天)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-削好皮的Pineapple! &#x1f468;‍&#x1f4bb; hello 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 削好皮的Pineapple! 原创 &#x1f468;‍&#x1f4b…

设计总监的“轻量化”新武器:用Adobe Express,音频一键驱动动画

在快节奏的创意项目中&#xff0c;如何将复杂的设计理念或冗长的研究报告&#xff0c;快速转化为易于理解、富有吸引力的动态内容&#xff0c;是衡量一个团队沟通效率的关键。作为一名在海外设计界工作了十余年的设计师&#xff0c;我发现&#xff0c;最高效的团队&#xff0c;…

零知开源——STM32F407VET6驱动SHT41温湿度传感器完整教程

✔零知开源是一个真正属于国人自己的开源软硬件平台&#xff0c;在开发效率上超越了Arduino平台并且更加容易上手&#xff0c;大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码&#xff0c;让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品&…

Linux流量分析:tcpdump wireshark

前言 最近因为工作需要&#xff0c;研究了下如何使用tcpdump和wireshark分析业务流量。如果要使用tcpdump分析具体的HTTP请求耗时&#xff0c;需捕获网络数据包并分析时间戳信息&#xff0c;重点关注TCP连接的建立、HTTP请求发送到响应接收的全过程。 以下是具体步骤和技巧&…

深度学习图像分类数据集—角膜溃疡识别分类

该数据集为图像分类数据集&#xff0c;适用于ResNet、VGG等卷积神经网络&#xff0c;SENet、CBAM等注意力机制相关算法&#xff0c;Vision Transformer等Transformer相关算法。 数据集信息介绍&#xff1a;角膜溃疡识别分类&#xff1a;[dot, mix, slice] 训练数据集总共有270张…