JDK源码——ThreadLocal

news2024/9/21 3:12:57

在这里插入图片描述

提供的方法

remove(): 移除当前线程的局部变量值。调用此方法后,当前线程将不再持有任何局部变量值。
set(T): 为当前线程设置一个新的局部变量值。参数T是要设置的值的类型。
get(): 获取当前线程的局部变量值。返回类型为T。
withInitial(Supplier): 创建一个新的ThreadLocal实例,其初始值由提供的Supplier提供。这个方法返回一个新的ThreadLocal对象,其类型为S。

应用场景

ThreadLocal主要应用于以下场景:

  1. 数据库连接管理:在多线程环境下,每个线程都需要独立的数据库连接。使用ThreadLocal可以确保每个线程都有自己的数据库连接,避免了多个线程共享同一个连接导致的问题。

  2. SimpleDateFormat线程安全问题:SimpleDateFormat类不是线程安全的,如果在多线程环境下使用同一个SimpleDateFormat实例进行日期格式化,可能会导致数据错乱。通过使用ThreadLocal为每个线程提供一个独立的SimpleDateFormat实例,可以避免这个问题。

  3. Spring事务管理:Spring框架中的事务管理器(TransactionManager)通常使用ThreadLocal来存储当前线程的事务信息。这样可以确保在多线程环境下,每个线程都能正确地处理自己的事务。

  4. 用户身份验证和会话管理:在Web应用程序中,通常需要对每个用户进行身份验证并维护用户的会话信息。使用ThreadLocal可以在每个线程中存储用户的身份信息和会话信息,使得这些信息在整个请求处理过程中都可以被访问到。

  5. 缓存管理:在多线程环境下,可以使用ThreadLocal来存储每个线程的缓存数据,避免不同线程之间的缓存数据冲突。

示例

下面是一个使用ThreadLocal的简单示例,演示了如何在多线程环境下为每个线程提供独立的数据库连接:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnectionManager {
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

    public static Connection getConnection() throws SQLException {
        Connection connection = connectionHolder.get();
        if (connection == null) {
            // 创建一个新的数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
            connectionHolder.set(connection);
        }
        return connection;
    }

    public static void closeConnection() throws SQLException {
        Connection connection = connectionHolder.get();
        if (connection != null) {
            connection.close();
            connectionHolder.remove();
        }
    }
}

在这个示例中,我们使用了ThreadLocal来存储每个线程的数据库连接。当调用getConnection()方法时,首先检查当前线程是否已经有一个数据库连接,如果没有,则创建一个新的连接并将其存储在ThreadLocal中。这样,每个线程都可以独立地访问和关闭自己的数据库连接,避免了资源共享的问题。

原理

ThreadLocal的实现原理主要基于Java的线程局部变量。每个线程都有一个自己的线程局部变量副本,这个副本只能由当前线程访问,其他线程无法访问。

具体来说,ThreadLocal通过创建一个静态的内部类ThreadLocalMap来实现线程局部变量的存储。每个线程都会拥有一个自己的ThreadLocalMap实例,这个实例中存储了线程的局部变量。ThreadLocalMap是一个自定义的哈希映射表,它的键是ThreadLocal对象,值是线程的局部变量。

当调用ThreadLocal的set方法时,它会将值存储在当前线程的ThreadLocalMap中,以ThreadLocal对象作为键。而get方法则是从当前线程的ThreadLocalMap中获取与ThreadLocal对象关联的值。

由于每个线程都有自己独立的ThreadLocalMap,因此每个线程只能访问到自己的局部变量,从而实现了线程之间的隔离。

需要注意的是,虽然ThreadLocal可以实现线程之间的隔离,但它并不能解决多线程环境下的资源共享问题。如果多个线程共享同一个ThreadLocal实例,那么它们仍然会共享相同的值。因此,在使用ThreadLocal时,需要确保每个线程都有自己的ThreadLocal实例,以避免资源竞争和数据不一致的问题。

另外,ThreadLocal还提供了一些额外的方法,如remove和withInitial。remove方法用于从当前线程的ThreadLocalMap中移除与ThreadLocal对象关联的值,以便及时释放资源。而withInitial方法则用于创建一个新的ThreadLocal实例,并指定一个初始值。

源码阅读

属性

public class ThreadLocal<T> {
    /**
     * ThreadLocals依赖于每个线程附加的线性探测哈希映射(Thread.threadLocals和inheritableThreadLocals)。
     * ThreadLocal对象充当键,通过threadLocalHashCode进行搜索。这是一个自定义哈希码(仅在ThreadLocalMap中有用),
     * 它可以消除连续构造的ThreadLocals被同一线程使用时的冲突,同时在较少见的情况下保持良好行为。
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 下一个要分配的哈希码。原子更新。从零开始。
     */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /**
     * 连续生成的哈希码之间的差值 - 将隐式顺序线程局部ID转换为接近最优分布的乘法哈希值,适用于2的幂大小的表。
     */
    private static final int HASH_INCREMENT = 0x61c88647;
}

set

这段代码是一个Java方法,用于设置当前线程中ThreadLocal变量的值。大多数子类不需要重写此方法,只需依赖initialValue方法来设置线程局部变量的值。

/**
 * 将当前线程中此线程局部变量的副本设置为指定值。大多数子类无需重写此方法,
 * 仅依赖{@link #initialValue}方法来设置线程局部变量的值。
 *
 * @param value 要存储在当前线程中此线程局部变量副本的值。
 */
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取与当前线程关联的ThreadLocalMap
    if (map != null) { // 如果ThreadLocalMap不为空
        map.set(this, value); // 在ThreadLocalMap中设置当前ThreadLocal对象的值
    } else {
        createMap(t, value); // 如果ThreadLocalMap为空,则创建一个新的ThreadLocalMap并设置值
    }
}

这个方法首先获取当前线程,然后尝试从与该线程关联的ThreadLocalMap中获取当前ThreadLocal对象的映射。如果映射存在,就在映射中设置值。如果映射不存在,就创建一个新的ThreadLocalMap并设置值。

get

/**
 * 返回当前线程中此线程局部变量的值。如果当前线程中该变量没有值,
 * 则首先将其初始化为调用{@link #initialValue}方法所返回的值。
 *
 * @return 此线程局部的当前线程值
 */
public T get() {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取与当前线程关联的ThreadLocalMap
    if (map != null) { // 如果ThreadLocalMap不为空
        ThreadLocalMap.Entry e = map.getEntry(this); // 从ThreadLocalMap中获取当前ThreadLocal对象的条目
        if (e != null) { // 如果条目不为空
            @SuppressWarnings("unchecked")
            T result = (T)e.value; // 获取条目中的值,并转换为泛型类型T
            return result; // 返回结果
        }
    }
    return setInitialValue(); // 如果没有找到值,则初始化并返回值
}

这个方法首先获取当前线程,然后尝试从与该线程关联的ThreadLocalMap中获取当前ThreadLocal对象的条目。如果找到了条目,就返回条目中的值。如果没有找到条目,就调用setInitialValue方法来初始化值,并返回这个初始值。

getMap和createMap

明白,以下是翻译后的内容:

/**
 * 获取与ThreadLocal关联的映射。在InheritableThreadLocal中被覆盖。
 *
 * @param  t 当前线程
 * @return 映射
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
 * 创建与ThreadLocal关联的映射。在InheritableThreadLocal中被覆盖。
 *
 * @param t 当前线程
 * @param firstValue 映射的初始条目的值
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

getMap方法用于获取当前线程上的ThreadLocal映射。这个映射包含了与该线程绑定的ThreadLocal变量的副本。如果在InheritableThreadLocal类中有相应的实现,那么这个方法将会被重写。

createMap方法用于在当前线程上创建一个新的ThreadLocal映射,并将传入的firstValue作为这个映射的初始值。这个映射会将当前ThreadLocal对象作为键,firstValue作为值。如果在InheritableThreadLocal类中有相应的实现,那么这个方法也会被重写。

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

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

相关文章

Java语言程序设计基础篇_编程练习题*16.21(秒表倒计时)

目录 题目&#xff1a;*16.21&#xff08;秒表倒计时&#xff09; 习题思路 代码示例 结果展示 题目&#xff1a;*16.21&#xff08;秒表倒计时&#xff09; 编写一个程序&#xff0c;允许用户在文本域中以秒为单位输入时间&#xff0c;然后按下Enter键来进行倒计时&#xff0…

剪映新手必看!剪映如何实现图片/视频在文字上显示(剪映如何实现图片/视频轨道在文字轨道之上显示/剪映如何实现自由轨道/层级)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 剪映 📒📝 解决方案⚓️ 相关链接 ⚓️📖 介绍 📖 剪映,这款广受好评的视频编辑应用,以其用户友好的界面和强大的功能,成为了许多视频创作者的首选。抱着好奇心,今天我也试用了一下,但在使用过程中,我遇到了一个问…

AI for reading ML paper

心流 心流 Kimi Kimi Humata Humata Bytez Bytez Chatgpt4 scholar 学术版chatgpt4&#xff0c;需要充值&#xff1b; 还有更多AI工具等待你发现&#xff1b;

LVS的NAT模式实战

目录 1.NAT模式的工作原理 2.NAT模式实战---环境准备 1.环境规划 2.克隆主机&#xff0c;生成nat模式的机器 1.设置主机名称 2.更改ip地址 3.添加网卡 4.查看网卡 5.修改网卡配置文件 6.时间同步 7.停用其他服务 8.添加web服务 1.安装nginx 2.修改index.html文件 …

NFS实现多服务器文件的共享

文章目录 一、简介二、部署1、准备1、服务端和客户端&#xff1a;安装nfs-utils2、服务端&#xff1a;创建共享目录3、服务端&#xff1a;配置exports文件4、客户端挂载5、客户端&#xff1a;卸载 三、附录1、NFS服务基本命令2、/etc/exports参数解释3、exportfs命令 参考资料 …

Mysql-B树和B+树的区别

当我们为ID去建立一个主键索引的时候&#xff0c;Mysql底层就会为我们去维护一棵树的结构&#xff0c;从而提升我们的数据检索效率&#xff0c;树的共同特性&#xff1a;小的索引在左边&#xff0c;大的索引在右边&#xff0c;每一次结点的寻址&#xff0c;都是一次磁盘的IO&am…

STM32 F103C8T6学习笔记19:定时器读取旋转编码器

今日学习STM32 F103C8T6 单片机驱动读取旋转编码器&#xff0c;并传输数据给串口&#xff1a; 文章提供测试代码讲解、完整工程下载、测试效果图 目录 旋转编码器&#xff1a; 输入捕获&#xff1a; 硬件连接&#xff1a; 代码编程&#xff1a; 测试效果视频: 测试工程下载&…

Mybatis(2)

目录 一. 参数传递 1. 单个参数 2. 多个参数 二. mybatis增删改 1. 新增 2. 删除 3. 修改 三. mybatis查询 1. 单张表查询 2. 多表查询 2.1 查询单个学生信息 2.2 mybatis自动映射级别 2.3 查询所有学生信息 3. 嵌套查询 3.1 查询单个学生信息 3.2 查询多个学生…

DDColor部署安装,在服务器Ubuntu22.04系统——点动科技

DDColor图片上色项目的部署安装&#xff0c;在服务器Ubuntu22.04系统——点动科技 一、ubuntu22.04基本环境配置1.1 更换清华Ubuntu镜像源1.2 更新包列表&#xff1a;2. 安装英伟达显卡驱动2.1 使用wget在命令行下载驱动包2.2 更新软件列表和安装必要软件、依赖2.2 卸载原有驱动…

24.8.9.11数据结构|链栈和队列

链栈 1、理解 实际上是一个仅在表头进行操作的单链表,头指针指向栈顶结点或头结点,以下恋栈均指带头结点的链栈. 2、 基本操作 1、定义结构&#xff1a;节点含有数据域和指针域 2、初始化操作&#xff1a;建立一个带头结点的空栈 3、取栈顶元素操作&#xff1a;取出栈的栈顶元…

Mybatis_springboot与mybatis-plus

一 Mybatis_springboot MyBatis 是一个流行的持久层框架&#xff0c;可以与 Spring Boot 无缝集成。下面是如何在 Spring Boot 项目中使用 MyBatis 的基本步骤。 1. 创建 Spring Boot 项目 你可以使用 Spring Initializr 创建一个新的 Spring Boot 项目。选择以下依赖项&…

只要15分钟,TiDB国产化数据库快速入门,简单高效

TiDB国产化数据库快速入门 1.TiDB服务部署2.TiDB服务核心组件说明2.1 PD&#xff08;Placement Driver&#xff09;- 交通指挥中心2.2 TiDB&#xff08;SQL Layer&#xff09;- 餐厅前台2.3 TiKV&#xff08;分布式存储层&#xff09;- 仓库存储系统2.4 TiFlash&#xff08;列式…

java数据库连接池介绍与使用

一. 使用数据库连接池有什么优势 在程序开发的过程中如果不使用数据库连接池 , 可能会导致我们每执行一次sql语句 , 就会向数据库发送请求链接与断开 , 这会消耗我们的内存资源 1. 资源重用更佳 减少开销&#xff1a;数据库连接池通过复用现有的数据库连接&#xff0c;显著减…

冒泡,选择,插入,希尔,快速,归并

冒泡&#xff0c;选择&#xff0c;插入&#xff0c;希尔&#xff0c;快速&#xff0c;归并 选择类的排序&#xff1a;选择排序&#xff0c;堆排序 交换类的排序&#xff1a;冒泡&#xff0c;快排 #include <stdio.h> #include<stdbool.h> #include<stdlib.h&…

Hidden Marlov Model(HMM)

一、Model 1、将声学特征设为X&#xff0c;经过语音识别得到的tokens设为Y&#xff0c;目标是找到通过X得到Y的最大概率&#xff0c;可以通过概率公式改变为 分为两个概率 2、将tokens序列Y转化为states序列S&#xff0c;声学特征分得更细 3、从states到声学特征的过程 二、HM…

力扣面试经典算法150题:罗马数字转整数

罗马数字转整数 今天的题目是力扣面试经典150题中的数组的简单题: 罗马数字转整数 题目链接&#xff1a;https://leetcode.cn/problems/roman-to-integer/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 将一个罗马数字转换成相应的整数。输入是一…

面向未来的低代码开发:人工智能与机器学习的结合

引言 在当今飞速发展的数字化世界中&#xff0c;技术的进步正以前所未有的速度改变着各个行业的运营方式。其中&#xff0c;低代码开发和人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;这两大技术的结合&#xff0c;正在推动着软件开发领域的又一次变…

身份证OCR识别接口如何用Java调用

一、什么是身份证OCR识别接口&#xff1f; 身份证OCR识别接口又叫身份证识别&#xff0c;身份证图像识别&#xff0c;身份证文字识别&#xff0c;即自动识别和提取身份证上的文字和数字信息。它可以通过图像处理和模式识别算法&#xff0c;将身份证中的姓名、性别、民族、出生…

我们从过去一年的大模型构建过程中学到的经验

当下正是使用大型语言模型&#xff08;LLM&#xff09;构建应用的好时机。过去一年&#xff0c;LLM 已经发展到了足够用于实际应用的水平。LLM 的进化速度与社交媒体层出不穷的演示应用&#xff0c;将在 2025 年吸引对 AI 领域的约 2000 亿美元投资。LLM 的门槛也很低&#xff…

Dbeaver 直连数据库下载数据到本地

参考文章&#xff1a;win11中DBeaver超详细下载与数据库MySQL 要使用 DBeaver 直接连接数据库并将数据下载到本地&#xff0c;您可以按照以下步骤操作&#xff1a; 1. 连接到数据库 打开 DBeaver。点击左上角的“Database”菜单&#xff0c;选择“New Database Connection”…