Java ThreadLocal 源码解析

前言

ThreadLocal 是 Java 语言中的一个类,可以使用它为每个线程存储数据。这些数据只能被当前线程访问,而其他线程无法访问。这个类可以用于避免多次传递、线程间数据隔离、事务操作等场景。

本次源码分析基于 JDK 21.0.1。

ThreadLocal 使用简介

基本操作

使用 ThreadLocal 时,可以将数据存储在一个特殊的对象中,这个对象会被自动关联到当前线程。例如,可以使用以下代码创建一个 ThreadLocal 对象,其中存储了一个整数值:

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
threadLocalValue.set(1);
Integer result = threadLocalValue.get();

如果想要在创建 ThreadLocal 对象时就设置初始值,可以使用 withInitial() 方法,并通过 lambda 表达式传入一个 Supplier 对象,例如:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

如果想要删除 ThreadLocal 中的值,可以调用 remove() 方法。例如:

threadLocal.remove();

多线程下 ThreadLocal 使用

以下代码演示了 ThreadLocal 的使用,代码首先创建了 NUM_THREADS 个线程,然后在每个线程内创建了 ThreadLocal。随后,每个线程分别对线程私有的 ThreadLocal 自增 NUM_THREADS 次,并对共享的 sharedValue 自增 NUM_THREADS 次。

import java.util.concurrent.atomic.AtomicInteger;public class Main {private static final int NUM_THREADS = 3;private static final int NUM_INCREMENTS = 5;public static void main(String[] args) {AtomicInteger sharedValue = new AtomicInteger(0);for (int i = 0; i < NUM_THREADS; i++) {new Thread(() -> {ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);for (int j = 0; j < NUM_INCREMENTS; j++) {int localValue = threadLocalValue.get();localValue++;threadLocalValue.set(localValue);int currentValue = sharedValue.get();currentValue++;sharedValue.set(currentValue);}System.out.println("Thread " + Thread.currentThread().getId() + ": Thread-local value = " + threadLocalValue.get() + ", Shared value = " + sharedValue.get());}).start();}}
}

ThreadLocal 源码解析

初始化

使用无参构造器时仅创建一个空的 ThreadLocal 对象:

    public ThreadLocal() {}

使用 withInitial 设置 ThreadLocal 初值时,返回的是 SuppliedThreadLocal 类型:

	// supplier 为传入的 lambda 表达式public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {// 创建并返回了一个 SuppliedThreadLocalreturn new SuppliedThreadLocal<>(supplier);}

传入的 Supplier 定义如下:

@FunctionalInterface
public interface Supplier<T> {T get();
}

其中 SuppliedThreadLocal 是 ThreadLocal 的静态内部类,它继承了 ThreadLocal 并重写了 initialValue() 方法:

    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {private final Supplier<? extends T> supplier;// 将赋初值的 lambda 表达式设置为 supplier 成员变量SuppliedThreadLocal(Supplier<? extends T> supplier) {this.supplier = Objects.requireNonNull(supplier);}@Overrideprotected T initialValue() {return supplier.get();}}

后续第一次调用 get() 时,会调用 SuppliedThreadLocal 重写的 initialValue() 方法,该方法调用了传入的 Supplier 表达式返回 ThreadLocal 初值。

set()

set() 用于设置 ThreadLocal 的值,其实现如下。

    public void set(T value) {// 为了设置 ThreadLocal 的值,传入了当前线程set(Thread.currentThread(), value);if (TRACE_VTHREAD_LOCALS) {dumpStackIfVirtualThread();}}private void set(Thread t, T value) {// 获取和当前 ThreadLocal 关联的哈希表ThreadLocalMap map = getMap(t);if (map == ThreadLocalMap.NOT_SUPPORTED) {throw new UnsupportedOperationException();}if (map != null) {// map 已经初始化,则直接设置值map.set(this, value);} else {// lazy 初始化 ThreadLocalMapcreateMap(t, value);}}

首先看 getMap(t),它获取了和当前 ThreadLocal 关联的哈希表:

    ThreadLocalMap getMap(Thread t) {// 从线程对象获取 ThreadLocalMap,由此可以看出每个对象一个 ThreadLocalMapreturn t.threadLocals;}

t.threadLocals 是 Thread 对象的成员,其类型为 ThreadLocal.ThreadLocalMap

public class Thread {...ThreadLocal.ThreadLocalMap threadLocals;...
}

ThreadLocalMap 是 ThreadLocal 类的内部类,它用于存储线程本地变量。 ThreadLocalMap 是 Thread 对象的成员变量,这说明每个线程都有一个 ThreadLocalMap 对象,而 ThreadLocalMap 保存了当前线程拥有的所有 ThreadLocal 对象和对应的变量副本。

回到set() 方法,由 set() 方法可以看出 ThreadLocalMap 是延迟到第一次使用的时候创建的。创建 ThreadLocalMap 的代码如下:

    void createMap(Thread t, T firstValue) {// 创建 ThreadLocal 并将关联的线程和赋予的值传入t.threadLocals = new ThreadLocalMap(this, firstValue);}

ThreadLocalMap 是一个专门保存 ThreadLocal 的哈希表,其构造器的实现如下:

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {// 创建哈希表的底层数组table = new Entry[INITIAL_CAPACITY];// 哈希值取余定位int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// 创建一个 entry 放到槽位中table[i] = new Entry(firstKey, firstValue);size = 1;// 设置哈希表扩容的大小门槛,为总容量的 2/3setThreshold(INITIAL_CAPACITY);}private void setThreshold(int len) {threshold = len * 2 / 3;}

get()

get() 方法用于获取 ThreadLocal 的值,其实现如下。

    public T get() {// 根据 Thread 获取值return get(Thread.currentThread());}// 1. 根据 Thread 获取 ThreadLocalMap// 2. 从 ThreadLocalMap 获取 entry,并将 entry 的 value 作为结果返回// 3. 如果 map 为 null,说明未初始化,调用 setInitialValue 进行初始化private T get(Thread t) {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(t);}private T setInitialValue(Thread t) {// 如果使用无参构造器,返回的是 null// 如果使用了 ThreadLocal.withInitial 创建 ThreadLocal,返回的是 lambda 表达式的结果T value = initialValue();// 获取 ThreadLocalMap,如果是第一次访问则进行初始化ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}if (this instanceof TerminatingThreadLocal<?> ttl) {TerminatingThreadLocal.register(ttl);}if (TRACE_VTHREAD_LOCALS) {dumpStackIfVirtualThread();}return value;}

总结

ThreadLocal 可以用于保存线程私有的数据,其源码具有下关键点:

  • ThreadLocalMap 的创建是懒加载的;
  • ThreadLocal 的实现是通过将一个 ThreadLocalMap 作为 Thread 对象的成员实现的;
  • 各个线程的全部 ThreadLocal 都保存在 ThreadLocalMap 中。

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

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

相关文章

vue-cli3/webpack打包时去掉console.log调试信息

文章目录 前言一、terser-webpack-plugin是什么&#xff1f;二、使用配置vue-cli项目 前言 开发环境下&#xff0c;console.log调试信息&#xff0c;有助于我们找到错误&#xff0c;但在生产环境&#xff0c;不需要console.log打印调试信息&#xff0c;所以打包时需要将consol…

servlet+jdbc实现用户注册功能

一、需求 在Servlet中可以使用JDBC技术访问数据库&#xff0c;常见功能如下&#xff1a; 查询DB数据&#xff0c;然后生成显示页面&#xff0c;例如&#xff1a;列表显示功能。接收请求参数&#xff0c;然后对DB操作&#xff0c;例如&#xff1a;注册、登录、修改密码等功能。…

前端基本性能指标及lighthouse使用

文章目录 1、基本指标介绍2、Performace分析2.1 performance属性2.2 使用performace计算2.3 Resource Timing API2.4 不重复的耗时时段区分2.5 其他组合分析2.6 JS 总加载耗时2.7 CSS 总加载耗时 3、lighthouse基本使用3.1 使用Chrome插件lighthouse3.2 使用Chrome浏览器开发者…

【操作系统】测试四

文章目录 单选题填空题 单选题 在一个可变分区存储管理中&#xff0c;最佳适应算法是将空闲区表中的空闲区按【 正确答案: C】的次序排列。 A. 地址递增 B. 地址递减 C. 大小递增 D. 大小递减 动态重定位是在【 正确答案: B】进行的重定位。 A. 作业执行前 B. 作业执行过程中 …

【黄啊码】宝塔设置默认php版本无效?

宝塔面板切换默认PHP版本1.情形描述&#xff1a; 我在执行composer install 时提示PHP版本太低&#xff0c;查看了一下宝塔面板的PHP版本&#xff0c;发现有两个一个5.6一个7.3。虽然php5.6版本已经暂停了&#xff0c;但是执行composer install还是提示版本太低。 然后根据网…

信息泄露总结

文章目录 一、备份文件下载1.1 网站源码1.2 bak文件泄露1.3 vim缓存1.4 .DS_Store 二、Git泄露2.1 git知识点2.1 log2.2 stash 三、SVN泄露3.1 SVN简介3.2 SVN的文件3.3 SVN利用 四、Hg泄露 一、备份文件下载 1.1 网站源码 常见的网站源码备份文件后缀&#xff1a; tartar.gz…

hyperf console 执行

一、原理描述 hyperf中&#xff0c;不难发现比如自定义控制器中获取参数&#xff0c;hyperf.php中容器获取&#xff0c;传入的都是接口&#xff0c;而不是实体类。 这是因为框架中的配置文件有设置对应抽象类的子类&#xff0c;框架加载的时候将其作为数组&#xff0c;使用的…

零基础学Java第二天

复习回顾&#xff1a; 1.dos命令 dir 显示当前文件夹下面的所有的文件和文件夹 cd 切换目录的 mkdir 创建文件夹的 rd 删除文件夹的 del 删除文件 D: 切换盘符 cls 清屏 2.书写Java代码换行打印《静夜诗》这首古诗 class Demo1 { …

深入理解 C# 中的字符串比较:String.CompareTo vs String.Equals

深入理解 C# 中的字符串比较&#xff1a;String.CompareTo vs String.Equals 在处理字符串时&#xff0c;了解如何正确比较它们对于编写清晰、有效和可靠的 C# 程序至关重要。本文将深入探讨 C# 中的两个常用字符串比较方法&#xff1a;String.CompareTo 和 String.Equals&…

Mybatis行为配置之Ⅲ—其他行为配置项说明

专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL Mybatis配置入门 Mybatis行为配置之Ⅰ—缓存 Mybatis行为配置…

电子工程师如何接私活赚外快?

对电子工程师来说&#xff0c;利用业余时间接私活是个很常见的技术&#xff0c;不仅可以赚取额外收入&#xff0c;也能提升巩固技术&#xff0c;可以说国内十个工程师&#xff0c;必有五个在接私活养家糊口&#xff0c;如果第一次接私活&#xff0c;该如何做&#xff1f; 很多工…

再升级|川石教育鸿蒙应用开发4.0教程发布

全新鸿蒙蓄势待发 HarmonyOS是一款面向未来的全场景分布式智慧操作系统。 对于消费者而言&#xff0c;HarmonyOS用一个统一的软件系统从根本上解决消费者面对大量智能终端体验割裂的问题&#xff0c;为消费者带来统一、便利、安全的智慧化全场景体验。 对于开发者而言&#xf…

十二:爬虫-Scrapy框架(上)

一&#xff1a;Scrapy介绍 1.Scrapy是什么&#xff1f; Scrapy 是用 Python 实现的一个为了爬取网站数据、提取结构性数据而编写的应用框架(异步爬虫框架) 通常我们可以很简单的通过 Scrapy 框架实现一个爬虫&#xff0c;抓取指定网站的内容或图片 Scrapy使用了Twisted异步网…

15. Mysql 变量的使用

目录 变量的概述自定义变量系统变量查看系统变量系统变量赋值 局部变量总结参考资料 变量的概述 MySQL支持不同类型的变量&#xff0c;包括自定义变量、系统变量和局部变量。自定义变量是在会话中定义的变量&#xff0c;用于存储临时数据。系统变量是MySQL服务器提供的全局变量…

【C#】关于事件的使用ii,eg:{婚礼策划公司的组织者}

场景&#xff1a; 假设你是一家婚礼策划公司的组织者&#xff0c;你需要安排婚礼的各个方面&#xff0c;如音乐、鲜花、照片等。 事件的用法&#xff1a; 你创建了一个婚礼策划事件&#xff0c;并邀请了音乐师、花店、摄影师等作为订阅者。当婚礼日期临近时&#xff0c;你触发婚…

(切图笔记)layui表格单元格添加超链接 以及传参方法 亲测可用 附代码

layui在切图网日常的工作中常常用到&#xff0c;特别是它的layer弹窗&#xff0c;基本可以满足网站切图时候遇到的绝大多数弹窗的情况&#xff0c;参数比较丰富 灵活&#xff0c;是不可多得的网页插件之一&#xff0c;我见很多人说layui过时了&#xff0c;这是相比于vue正流行的…

Linux创建Macvlan网络

最近在看Docker的网络&#xff0c;测试Macvlan部分时&#xff0c;发现Docker创建Macvlan与预期测试结果不一样。所以查阅了Linux下配置Macvlan&#xff0c;记录如下。 参考 1.Linux Macvlan 2.图解几个与Linux网络虚拟化相关的虚拟网卡-VETH/MACVLAN/MACVTAP/IPVLAN 3.创建ma…

Java8 - 更优雅的字符串连接(join)收集器 Collectors.joining

Java8中的字符串连接收集器 在JDK8中&#xff0c;可以采用函数式编程&#xff08;使用 Collectors.joining 收集器&#xff09;的方式对字符串进行更优雅的连接。 Collectors.joining 收集器 支持灵活的参数配置&#xff0c;可以指定字符串连接时的 分隔符&#xff0c;前缀 和…

Sentinel-3如何处理并下载LST数据-陆地表面温度”(Land Surface Temperature)

LST 通常指的是“陆地表面温度”&#xff08;Land Surface Temperature&#xff09;。陆地表面温度是指地球表面上陆地部分的温度&#xff0c;而不包括水体表面。LST 是遥感技术中一个重要的参数&#xff0c;可以通过卫星遥感等手段进行测量和监测。 陆地表面温度对于许多领域…

浅谈高并发以及三大利器:缓存、限流和降级

引言 高并发背景 互联网行业迅速发展&#xff0c;用户量剧增&#xff0c;系统面临巨大的并发请求压力。 软件系统有三个追求&#xff1a;高性能、高并发、高可用&#xff0c;俗称三高。三者既有区别也有联系&#xff0c;门门道道很多&#xff0c;全面讨论需要三天三夜&#…