说说ThreadLocal的实现原理

ThreadLocal是什么?

ThreadLocal是Java中的一个类,用于创建线程局部变量解决线程安全。每个线程都有自己独立的变量副本,彼此之间互不影响。它的主要作用是在多线程环境下,确保每个线程都有自己的变量实例,避免了变量共享带来的线程安全问题。

ThreadLocal 的主要功能

  1. 线程局部变量:每个线程都有自己的变量副本,互不干扰。
  2. 线程安全:避免了多线程环境下的竞争和冲突。

ThreadLocal 的核心方法

  • Thread.currentThread()获取当前线程。
  • getMap(t)获取当前线程的ThreadLocalMap
  • map.getEntry(this)ThreadLocalMap中获取以当前ThreadLocal为键的条目(Entry)。
  • 如果条目存在,返回其值;否则调用setInitialValue()进行初始化。
set(T value)

set(T value) 方法用于设置当前线程的局部变量值,具体实现如下:

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
  • Thread.currentThread()获取当前线程。
  • getMap(t)获取当前线程的ThreadLocalMap
  • 如果ThreadLocalMap存在,调用map.set(this, value)设置值;否则调用createMap(t, value)创建一个新的ThreadLocalMap
remove()

remove() 方法用于移除当前线程的局部变量值,具体实现如下:

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
  • 获取当前线程的ThreadLocalMap
  • 如果ThreadLocalMap存在,调用m.remove(this)移除条目。

ThreadLocal 的典型使用场景

  1. 用户会话管理:在web应用中,存储与当前线程(用户请求)相关的信息,如用户会话、请求上下文等。
  2. 数据库连接:为每个线程分配独立的数据库连接,避免连接共享带来的线程安全问题。
  3. 事务管理:存储与当前线程相关的事务信息,如事务状态、事务ID等。
  4. 格式化工具:存储与当前线程相关的工具实例,如SimpleDateFormat,避免工具共享带来的线程安全问题。

ThreadLocal 的实现原理

ThreadLocal 是 Java 中用于实现线程局部变量的类,它为每个线程提供一个独立的变量副本。实现这一点的关键在于每个线程都有一个 ThreadLocalMap 对象,ThreadLocalMap 类似于一个哈希表,存储了当前线程所对应的所有 ThreadLocal 变量及其值。理解 ThreadLocal 的实现原理,需要深入探讨其核心机制及内部结构。以下是详细的解释:

1. ThreadLocal 类的基本结构

ThreadLocal 类本身非常简单,主要包含以下几个重要的方法:

  • get(): 获取当前线程的局部变量值。
  • set(T value): 设置当前线程的局部变量值。
  • remove(): 移除当前线程的局部变量值。

这些方法都依赖于每个线程独有的 ThreadLocalMap

2. ThreadLocalMap

ThreadLocalMapThreadLocal 的一个静态内部类,它是一个自定义的哈希表,用于存储线程局部变量。每个线程都有一个 ThreadLocalMap 实例,存储在线程对象中。其实现机制如下:

  • 存储位置ThreadLocalMap存储在每个线程的Thread对象中,具体来说,ThreadLocalMap是Thread类的一个实例变量:
public class Thread {// 部分源码省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;// 部分源码省略...
}
  • 键和值ThreadLocalMap的键是ThreadLocal对象,值是实际存储的数据,类型为Object。为了避免ThreadLocal对象的内存泄漏,ThreadLocalMap的键使用的是弱引用(WeakReference<ThreadLocal<?>>)。

3. ThreadLocalMap 的内部结构

ThreadLocalMap 是一个自定义的哈希表,主要包含以下结构:

  • EntryThreadLocalMap的内部静态类,用于存储键值对。键是弱引用的ThreadLocal对象,值是实际数据。而Entry是以数组的形式存在,在源码中的体现就是Entry数组成员变量table(private Entry[] table;),也就是说每个ThreadLocalMap可以保存多个ThreadLocal作为键,值可以设置为任意你想与该ThreadLocal进行关联的值()。
/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread.  To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/
static class ThreadLocalMap{/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;/*** The next size value at which to resize.*/private int threshold;// 部分源码省略...
}
  • 哈希冲突处理ThreadLocalMap使用线性探测法解决哈希冲突。当发生哈希冲突时,会依次检查下一个位置,直到找到空位置或匹配的键。
  • 垃圾回收:由于键是弱引用,当ThreadLocal对象没有其他强引用时,会被垃圾回收器回收。此时,ThreadLocalMap的键会变成null,但值仍然存在。因此,需要显式地调用remove()方法或依赖ThreadLocalMap的内部机制来清理这些条目。

4.Thread的成员变量ThreadLocalMap

每个线程 (Thread 对象) 都有一个 ThreadLocalMap 实例。具体来说,Thread 类有一个 ThreadLocalMap类型的threadLocals 字段,用于保存当前线程的 ThreadLocalMap, 也就是负责管理当前线程的变量副本。因为ThreadLocalMap可以保存多个不同的ThreadLocal对象作为键,值为任意内容的键值对,所以每个线程可以保存多个变量副本,数量上限取决于ThreadLocal对象的个数。

public
class Thread implements Runnable {// 部分源码省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;// 部分源码省略...
}

5. 如何为每个线程提供一个独立的变量副本,实现线程安全?

在多线程环境中, 每个线程操作 ThreadLocal 类时,只影响到自己线程的 ThreadLocalMap 里面的内容,而不会干扰到其他线程的 ThreadLocalMap。 如果应用程序中只使用一个ThreadLocal,那么每个线程内部的 ThreadLocalMap 都保存着相同的 ThreadLocal对象作为键,值可以设置成任意内容。如果应用程序中使用了多个ThreadLocal,那么每个线程内部的 ThreadLocalMap 都保存着多个 ThreadLocal对象 作为键,值为 每个ThreadLocal对象所关联对应的内容,每个线程也就保存了多个独立的变量副本。因此也就实现了每个线程都有自己的独立变量副本。

接下来我们看看ThreadLocal的set()方法和get()方法源码

get()方法

/*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);// 如果当前线程的ThreadLocalMap不为nullif (map != null) {// 从ThreadLocalMap的Entry类型数组table中获取以当前// ThreadLocal作为键的Entry实例ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {// Entry实例e不为null,返回其value@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 给当前线程t的ThreadLocalMap设置初始值return setInitialValue();
}/*** Get the entry associated with key.  This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss.  This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param  key the thread local object* @return the entry associated with key, or null if no such*/
private Entry getEntry(ThreadLocal<?> key) {// 计算索引值,用于在table中定位Entryint i = key.threadLocalHashCode & (table.length - 1);// 获取当前位置的EntryEntry e = table[i];// 检查当前位置的Entry是否有效且其存储的ThreadLocal对象等于keyif (e != null && e.get() == key) {// 直接命中,返回Entryreturn e;} else {// 未命中或Entry已失效,则通过getEntryAfterMiss进一步处理return getEntryAfterMiss(key, i, e);}
}

set()方法

/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.
*/
public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)//如果当前线程的ThreadLocalMap不为null// 设置key为当前ThreadLocal的值为valuemap.set(this, value);else//如果当前线程的ThreadLocalMap为nullcreateMap(t, value);//初始化一个ThreadLocalMap,键为当前ThreadLocal
}//  .../*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

6. 内存泄露问题

由于ThreadLocalMap的键是弱引用,ThreadLocal对象被回收后,键会变成null,但值仍然保留在内存中,导致内存泄露。因此,建议在不再使用 ThreadLocal 时显式调用 remove() 方法,以确保清理数据。

7. 总结

每个线程在使用 ThreadLocal 类时,操作的是自己线程内部的 ThreadLocalMap,这确保了线程之间的隔离:

  • 独立性:每个线程拥有自己的 ThreadLocalMap 实例,因此对 ThreadLocal 的操作不会相互干扰。
  • 线程安全:由于每个线程有独立的 ThreadLocalMap,不存在并发访问 ThreadLocalMap 的问题,因此操作是线程安全的。

这种设计使得 ThreadLocal 非常适合在多线程环境下使用,用于存储线程私有的变量,从而避免了线程间的数据共享问题。

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

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

相关文章

Retrofit类型安全的HTTP客户端库(json)

简介 Retrofit是Square公司开发的一个类型安全的HTTP客户端库&#xff0c;用于Android和Java平台&#xff0c;它使得与Web服务的交互变得更加简单快捷。Retrofit将HTTP API转换成Java接口&#xff0c;让你可以用更简洁的代码形式调用RESTful API&#xff0c;Android网络编程重点…

在前端开发过程中如果函数参数很多,该如何精简

1. 在前端开发过程中如果函数参数很多&#xff0c;该如何精简 1.1. 对象参数&#xff08;对象字面量&#xff09;&#xff1a;1.2. 默认参数和解构赋值&#xff1a;1.3. 使用类或构造函数&#xff1a;1.4. 利用闭包或者高阶函数&#xff1a;1.5. 利用ES6的扩展运算符&#xff1…

【LeetCode】每日一题:反转链表

题解思路 循环的方法需要注意prev应该是None开始&#xff0c;然后到结束的时候prev是tail&#xff0c;递归的思路很难绕过弯来&#xff0c;主要在于很难想清楚为什么可以返回尾节点&#xff0c;需要多做递归题&#xff0c;以及递归过程中&#xff0c;可以不使用尾节点来找当前…

Nuxt3 的生命周期和钩子函数(二)

title: Nuxt3 的生命周期和钩子函数&#xff08;二&#xff09; date: 2024/6/26 updated: 2024/6/26 author: cmdragon excerpt: 摘要&#xff1a;本文深入介绍了Nuxt.js框架中几个关键的生命周期钩子函数&#xff0c;包括app:redirected&#xff08;SSR环境下重定向前触发…

20240626让飞凌的OK3588-C开发板在相机使用1080p60分辨率下预览

20240626让飞凌的OK3588-C开发板在相机使用1080p60分辨率下预览 2024/6/26 15:15 4.2.1 全编译测试 在源码路径内&#xff0c;提供了编译脚本 build.sh&#xff0c;运行该脚本对整个源码进行编译&#xff0c;需要在终端切换到解压 出来的源码路径&#xff0c;找到 build.sh 文件…

6.26作业

1.整理思维导图 2.统计家目录下.c文件的个数 ls ~/*.c | wc -l 3.终端输入一个.sh文件&#xff0c;判断文件是否由可执行权限&#xff0c;如果有可执行权限运行脚本&#xff0c;没有可执行权限添加可执行权限后&#xff0c;再运行脚本 #!/bin/bash read -p "请输入一个.…

spring模块(二)SpringBean(2)InitializingBean

一、介绍 InitializingBean是Spring框架提供的一个接口&#xff0c;用于在Bean初始化完成后执行特定的初始化逻辑。 二、使用 1、使用方法 1.1、实现InitializingBean接口 可以让Bean实现该接口&#xff0c;并重写其afterPropertiesSet()方法 1.2、注册 也即让bean初始化…

从官方源码精简出第1个FreeRTOS程序

一、下载官方源码 1、打开百度搜索freerots&#xff0c;找到官网:FreeRTOS官网 2、将源码解压到没有中文目录的路径下 二、删减目录 1、删除FreeRTOS-Plus和tools 2、删除FreeRTOS/Demo下除CORTEX_STM32F103_Keil外的所有文件 3、删除FreeRTOS\Source\portable下除RVDS和MemM…

vue2面试题——API

1. $set this.$set(目标对象target&#xff0c;改的位置&#xff0c;最终数据) /* 数据更新了而视图没有更新的情况 */ <template><div>{{ arr }}<button clickbtn>按钮</button></div> </template> <script> export default {name:…

海康威视摄像头修复

一、适用场景 1、室外安装的摄像头&#xff0c;长时间日晒雨淋后&#xff0c;可能因风向导致雨水进入水晶头&#xff0c;进而摄像头无法识别&#xff1b; 2、在经常施工的场地&#xff0c;可能由于车辆的进出&#xff0c;或施工设备的运行导致摄像头的网线水晶头断裂而无法使用…

浔川社团正式启用 代码付费制度——浔川总社部

浔川社团正式启用 代码付费制度。 规则&#xff1a; 浔川社团源代码收费标准表&#xff08;1&#xff09; 1-5行代码0.2元/行1-10行代码0.3元/行1-20行代码0.5元/行 浔川社团源代码收费标准表&#xff08;2&#xff09; 1-30行代码0.6元/行1-40行代码0.8元/行1-50行代码0.09元…

【PythonWeb开发】Flask中间件钩子函数实现封IP

在 Flask 框架中&#xff0c; 提供了几种类型的钩子&#xff08;类似于Django的中间件&#xff09;&#xff0c;它们是在请求的不同阶段自动调用的函数。这些钩子让你能够对请求和响应的处理流程进行扩展&#xff0c;而无需修改核心代码。 Flask钩子的四种类型 before_first_r…

IT入门知识第八部分《云计算》(8/10)

目录 云计算&#xff1a;现代技术的新篇章 1. 云计算基础 1.1 云计算的起源和发展 云计算的早期概念 云计算的发展历程 1.2 云计算的核心特点 按需自助服务 广泛的网络访问 资源池化 快速弹性 按使用量付费 1.3 云计算的优势和挑战 成本效益 灵活性和可扩展性 维…

[leetcode]intersection-of-two-arrays-ii 两个数组的交集 II

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {sort(nums1.begin(), nums1.end());sort(nums2.begin(), nums2.end());int length1 nums1.size(), length2 …

动态规划——123. 买卖股票的最佳时机 III

目录 1、题目链接 2、题目分析 1.状态表示 2.状态转移方程 3.初始化 4.填表 5.返回值 3、代码解析 1、题目链接 123. 买卖股票的最佳时机 III 2、题目分析 1.状态表示 由题目可知&#xff0c;我们分为两种状态&#xff0c;买入和卖出&#xff0c;又因为只能完成两次交易…

windows下如何配置vs code的编译环境

在 Windows 上配置 VS Code 的编译环境涉及安装编译器、配置 VS Code 以及编写和运行代码。以下是具体的步骤&#xff1a; 步骤 1&#xff1a;安装必要的软件 安装 Visual Studio Code&#xff1a; 访问 VS Code 的官方网站并下载安装包。按照安装向导进行安装。 安装 C/C 编译…

盲源信道分离—FastICA算法性能仿真

本案例中使用Matlab软件对FastICA算法的声音分离性能进行了仿真&#xff0c;分别对简单波形的混合信号、不同类型声音的混合信号、同一类型的混合信号这三种情况进行仿真&#xff0c;主要从分离信号的波形形状、串音误差两方面对分离性能进行衡量&#xff0c;仿真结果显示快速I…

Gradle学习-3 Gradle构建的生命周期

Gradle常用文件目录 Gradle 构建的生命周期&#xff0c;有3个阶段: 初始化阶段配置阶段执行阶段 1、初始化阶段 Gradle 支持构建单个工程个多个子工程&#xff0c;初始化阶段主要负责收集所有参与本次构建的子工程&#xff0c;创建一个项目的层次结构&#xff0c;并未每个…

SpringBoot优点达项目实战:获取系统配置接口(三)

SpringBoot优点达项目实战&#xff1a;获取系统配置接口&#xff08;二&#xff09; 文章目录 SpringBoot优点达项目实战&#xff1a;获取系统配置接口&#xff08;二&#xff09;1、查看接口2、查看数据库3、代码实现1、创建实体类SysConfig2、创建返回数据的vo3、创建control…

【INTEL(ALTERA)】Eclipse Nios II SBT 无法从模板创建新应用程序和 BSP

目录 说明 解决方法 说明 您应该能够创建新的应用程序和 BSP 模板包含以下步骤&#xff1a; 选择 Nios II应用程序和 BSP 来自模板。选择您的.sopcinfo 文件并选择模板。从您的工作区单击 选择现有的 BSP 项目。单击 创建。选择所需的 BSP 选项。单击 完成。 但是&#xf…