ThreadLocal

qinyelin
发布于 2026-08-26 / 8 阅读
0
0

ThreadLocal

ThreadLocal 为什么会内存泄漏?

面试频率:★★★★★
工作频率:★★★★★


⚡30 秒速记(复习只看这里)

🟢 一句话

ThreadLocal 是线程本地变量,为每个线程保存独立的数据副本,避免多个线程共享数据导致线程安全问题。


🟡 底层原理

ThreadLocal 不负责保存数据。

数据实际存储:

Thread

↓

ThreadLocalMap

↓

Entry

↓

value

每个线程都有自己的 ThreadLocalMap。


🟠 为什么会内存泄漏?

ThreadLocalMap:

key = ThreadLocal(弱引用)

value = 用户数据(强引用)

当 ThreadLocal 没有强引用:

ThreadLocal对象被GC

↓

key变成null

↓

value还存在

↓

无法释放

导致内存泄漏。


🔴 解决方式

使用:

threadLocal.remove();

清理线程中的数据。


📚 完整知识

一、ThreadLocal 是什么?

ThreadLocal:

为每个线程提供独立变量副本。

例如:

线程A:

ThreadA

↓

userId = 100

线程B:

ThreadB

↓

userId = 200

两个线程数据互不影响。


二、为什么不用普通变量?

例如:

public class UserContext {

    private static Long userId;

}

多个线程:

线程A:

userId = 100


线程B:

userId = 200

线程B修改后:

线程A读取:

200

产生数据覆盖。


三、ThreadLocal 使用方式

public class UserContext {

    private static ThreadLocal<Long> userId =
            new ThreadLocal<>();


    public static void setUserId(Long id){

        userId.set(id);

    }


    public static Long getUserId(){

        return userId.get();

    }


    public static void remove(){

        userId.remove();

    }

}

流程:

请求进入

↓

threadLocal.set()

↓

业务代码任意位置 get()

↓

请求结束 remove()

四、ThreadLocal 底层结构

重点:

ThreadLocal 数据存储在线程里面,而不是 ThreadLocal 对象里面。

结构:

Thread

↓

ThreadLocalMap

↓

Entry

↓

value

例如:

线程A:

ThreadA

ThreadLocalMap

    |

 Entry

 key = ThreadLocal

 value = 用户A信息

线程B:

ThreadB

ThreadLocalMap

    |

 Entry

 key = ThreadLocal

 value = 用户B信息

虽然:

ThreadLocal 是同一个。

但是:

每个线程数据不同。


五、ThreadLocalMap 为什么不是 HashMap?

ThreadLocalMap 是 ThreadLocal 自己实现的一种 Map。

特点:

key 使用弱引用。


六、什么是弱引用?

普通引用:

ThreadLocal tl = new ThreadLocal<>();

强引用:

变量

↓

ThreadLocal对象

只要变量存在:

对象不会被 GC。


弱引用:

WeakReference

↓

对象

如果没有其他强引用:

GC 可以回收对象。


七、为什么会内存泄漏?

正常:

Thread

↓

ThreadLocalMap

↓

Entry

↓

key = ThreadLocal

value = 用户数据

如果 ThreadLocal 没有引用:

例如:

threadLocal = null;

GC:

ThreadLocal对象被回收

变成:

Thread

↓

ThreadLocalMap

↓

Entry

↓

key = null

value = 用户数据

问题:

Entry 仍然强引用 value。

所以:

value无法释放

产生内存泄漏。


八、为什么线程池环境更容易出现?

普通线程:

执行任务

↓

线程结束

↓

资源释放

问题较小。


线程池:

创建线程

↓

长期存在

↓

不断执行任务

例如 Tomcat:

线程1

处理请求A

↓

处理请求B

↓

处理请求C

如果不 remove:

请求A的数据可能残留在线程中。


九、为什么必须 remove?

正确:

try {

    threadLocal.set(userId);

    doBusiness();

} finally {

    threadLocal.remove();

}

保证:

请求结束

↓

清理线程变量

避免:

  • 内存泄漏
  • 用户数据串线

十、Spring 中 ThreadLocal 的应用

1. 用户登录信息

例如:

登录用户ID

↓

ThreadLocal

↓

Service

↓

Dao

不用每层传递:

service(userId)

2. Spring 事务

Spring 事务会绑定数据库连接。

类似:

当前线程

↓

ThreadLocal

↓

Connection

保证:

同一个事务中使用同一个数据库连接。


3. 链路追踪

例如:

traceId

↓

ThreadLocal

↓

日志

方便查询一次请求经过哪些服务。


💼 工作场景

错误:

threadLocal.set(userId);

但是:

没有:

threadLocal.remove();

在线程池环境:

可能:

请求A用户信息

↓

残留在线程

↓

请求B读取旧数据

产生严重问题。


🎤 面试回答(30秒)

ThreadLocal 是 Java 提供的线程本地变量,它为每个线程维护独立的数据副本,避免多个线程共享变量导致线程安全问题。ThreadLocal 底层数据实际存储在线程的 ThreadLocalMap 中,key 是 ThreadLocal 的弱引用,value 是强引用。如果 ThreadLocal 对象被回收,但是线程还存活,可能导致 key 为 null 而 value 无法释放,从而产生内存泄漏。因此在线程池环境中使用 ThreadLocal 后,需要及时调用 remove 方法清理。


🎯 面试追问

Q1:ThreadLocal 数据存在哪里?

答:

存在线程对象的 ThreadLocalMap 中。

不是 ThreadLocal 自己保存。


Q2:为什么 ThreadLocalMap 的 key 使用弱引用?

答:

防止 ThreadLocal 对象没有引用时无法被 GC。


Q3:为什么弱引用还会内存泄漏?

答:

因为 key 被回收后变成 null,但是 value 仍然被 Entry 强引用。


Q4:为什么线程池更容易出现问题?

答:

线程池线程长期存在,不会因为请求结束销毁。


Q5:为什么使用 ThreadLocal 后要 remove?

答:

清理 value,避免内存泄漏和数据串线。


⚠️ 工作踩坑

1. 使用 ThreadLocal 不 remove

问题:

内存泄漏

用户数据残留

2. 认为 ThreadLocal 是全局变量

错误:

ThreadLocal 是:

每个线程一份数据

3. 异步线程中直接使用 ThreadLocal

问题:

线程发生变化。

ThreadLocal 数据不会自动传递。

需要:

  • 手动传递
  • TransmittableThreadLocal 等方案

❓ 自测

  1. ThreadLocal 是干什么的?

  2. ThreadLocal 数据存在哪里?

  3. 为什么不用普通变量?

  4. ThreadLocalMap 的 key 为什么是弱引用?

  5. 为什么弱引用还会内存泄漏?

  6. 为什么线程池环境更容易出现问题?

  7. 使用 ThreadLocal 后为什么需要 remove?

  8. Spring 事务为什么会使用类似 ThreadLocal 的机制?


评论