当前位置: 首页 > news >正文

ThreadLocal源码解析

ThreadLocal也是面试题中十分常见的问题,我是在大部分人的启蒙项目苍穹外卖接触到ThreadLocal这个概念的,苍穹外卖把用户id存在ThreadLocal中来进行权限检验,这是我所了解到的ThreadLocal的第一个用处。

在上次字节面试时被面试官狠狠拷打了ThreadLocal,从概念问到了底层,而我只背了八股,了解到ThreadLocal底层是个map,有弱引用,在面试官反复的反问上败下阵来,今天又是我一个小实习生摸鱼的一天,决定看一看ThreadLocal源码,看一看他到底是何方神圣。

ThreadLocal简介

ThreadLocal 是 Java 中用于实现线程局部变量的类,它能够在多线程环境下,为每个线程提供独立的变量副本,从而避免线程间的数据竞争。

核心作用

  • 线程隔离:每个线程操作自己的变量副本,互不干扰。

  • 避免同步:不需要加锁(如 synchronizedsynchronized),提高并发性能。

  • 典型应用

    • Spring 的事务管理(TransactionSynchronizationManager)

    • 用户会话信息存储(如 Session)

    • JDBC 连接管理(避免线程间共享Connection)

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

 ThreadLocal原理

 ThreadLocal的set()方法

public void set(T value) {Thread t = Thread.currentThread();  // 获取当前线程ThreadLocalMap map = getMap(t);     // 获取线程的 ThreadLocalMapif (map != null) {map.set(this, value);  // 如果 map 已存在,直接设置值} else {createMap(t, value);   // 否则初始化 ThreadLocalMap}
}

 从上面的代码可以看出,ThreadLocal  set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。

那么第二个问题来了,ThreadLocalMap是什么?

ThreadLocalMap实现

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

可看出ThreadLocalMap是ThreadLocal的内部静态类,用于存储线程的局部变量,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用(常用考点)。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。每个线程都有一个独立的 ThreadLocalMap,存储该线程的所有 ThreadLocal 变量。

ThreadLocalMap的set()方法

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)]) {if (e.refersTo(key)) {  // 如果 key 已存在,更新 valuee.value = value;return;}if (e.refersTo(null)) {  // 如果 key 已被回收(弱引用),替换过期 EntryreplaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);  // 插入新 Entryint sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold) {rehash();  // 扩容}
}

ThreadLocalMap的getEntry()方法

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key) {return e;  // 直接命中} else {return getEntryAfterMiss(key, i, e);  // 线性探测查找}
}

ThreadLocal的get()方法

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);  // 获取当前 ThreadLocal 对应的 Entryif (e != null) {@SuppressWarnings("unchecked")T result = (T) e.value;return result;}}return setInitialValue();  // 如果不存在,初始化并返回默认值
}private T setInitialValue() {T value = initialValue();  // 默认返回 null,可重写Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}return value;
}

ThreadLocal的remove()方法

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {m.remove(this);  // 从 ThreadLocalMap 中移除当前 ThreadLocal}
}

调用 remove() 方法会直接从当前线程的 ThreadLocalMap 中删除对应的 ThreadLocal 键值对。这一操作的核心目的是防止内存泄漏。

ThreadLocal 的内存泄漏问题

ThreadLocalMap使用 ThreadLocal的弱引用作为 key,弱引用的特性是:如果某个对象仅被弱引用关联,垃圾回收(GC)时会自动清理该对象。因此,如果 ThreadLocal未被外部强引用持有,GC 时会回收 ThreadLocal实例,导致 ThreadLocalMap中的 key 变为 null。

然而,ThreadLocalMap中的 value 仍然是强引用,即使 key 被回收,value 也不会被自动清理。这会导致 ThreadLocalMap中存在大量 key 为 null但 value 仍占用内存的条目,从而引发内存泄漏。

线程复用与内存泄漏的关联

ThreadLocal的变量生命周期与线程绑定。在线程池场景下,线程通常会被复用,导致线程的生命周期可能极长(甚至与 JVM 生命周期一致)。如果未及时清理 ThreadLocal变量(如未调用 remove() 或替换值),其存储的数据会一直堆积。

ThreadLocal与Thread,ThreadLocalMap的关系

图片来自于史上最全ThreadLocal 详解(一)-CSDN博客。从图中可以看出三者之间的关系。

关于ThreadLocal的常见面试题

4.1 ThreadLocal 和 synchronized 的区别?

对比项ThreadLocalsynchronized
数据隔离方式每个线程独立副本共享数据 + 锁
性能无锁,更高性能有锁,可能阻塞
适用场景线程隔离数据(如 Session)线程共享数据(如计数器)

4.2 ThreadLocal 的 key 为什么是弱引用?

  • 防止内存泄漏

    • 如果 ThreadLocal被回收,Entry 的 key 会自动被 GC 清理,避免 ThreadLocal无法回收。

  • 但 value 仍需手动清理

    • 因为 value 是强引用,需要 remove() 或 set(null)。

4.3 ThreadLocal 的 key 为什么不设为强引用?

在业务代码中使用完ThreadLocal后,即使ThreadLocal引用被回收,由于ThreadLocalMap的Entry强引用了ThreadLocal(ThreadLocal作为key),导致ThreadLocal对象无法被回收。当没有手动删除Entry且当前线程仍在运行时,会形成一条强引用链:当前线程引用→当前线程→ThreadLocalMap→Entry。这个Entry包含了ThreadLocal实例和value,因此不会被回收,从而造成内存泄漏。换言之,由于ThreadLocalMap的key采用强引用机制,内存泄漏问题无法完全避免。

以上就是关于ThreadLocal的一些解析与想法。

http://www.dtcms.com/a/285081.html

相关文章:

  • Mac OS上docker desktop 替代方案
  • Linux 下按字节分割与合并文件
  • 压力大为啥想吃甜食
  • wireshark的常用用法
  • C++ Lambda 表达式详解:从入门到实战
  • Leetcode 03 java
  • 设备管理系统横评:预警功能、移动端体验、性价比谁更强?
  • PyTorch图像预处理全解析(transforms)
  • SAP-ABAP:SAP的‘cl_http_utility=>escape_url‘对URL进行安全编码方法详解
  • 6 基于STM32单片机的智能家居系统设计(STM32代码编写+手机APP设计+PCB设计+Proteus仿真)
  • 如何从 iPhone 向Mac使用 AirDrop 传输文件
  • 企业网络运维进入 “AI 托管” 时代:智能分析 + 自动决策,让云、网、端一眼看穿
  • 关于用git上传远程库的一些常见命令使用和常见问题:
  • Redis学习-02安装Redis(Ubuntu版本)、开启远程连接
  • ComfyUI 中RAM内存、VRAM显存、GPU 的占用和原理
  • 基于深度学习的图像识别:从零构建卷积神经网络(CNN)
  • 面对微软AD的安全隐患,宁盾身份域管如何设计安全性
  • Python调用父类方法的三种方式详解 | Python面向对象编程教程
  • 【DOCKER】-5 镜像仓库与容器编排
  • 云服务器如何设置防火墙和安全组规则?
  • Java EE进阶3:SpringBoot 快速上手
  • 【Linux】Makefile(二)-书写规则
  • 【原创】【图像算法】高精密电子仪器组装异常检测
  • 力扣119:杨辉三角Ⅱ
  • Cursor出现This model provider doesn’t serve your region解决方案
  • 【调度算法】
  • javaScript中数组常用的函数方法
  • 洛谷 P1601 A+B Problem(高精)
  • 重构比特币在 Sui DeFi 中的角色
  • Redis中什么是看门狗机制