写点什么

反问面试官 3 个 ThreadLocal 的问题

  • 2024-09-24
    福建
  • 本文字数:2104 字

    阅读完需:约 7 分钟

ThreadLocal,一个 Java 人面试绕不开的话题,我也很奇怪为什么那些面试官很喜欢问这个,也不知道他们自己有没有搞清楚。


接下来,我想先说说 ThreadLocal 的用法和使用场景,然后反问面试官 3 个关于 ThreadLocal 的话题


使用方法和场景


一句话总结:ThreadLocal是给每个线程准备一份“独立的小空间”,它让每个线程都拥有自己独立的变量副本。在多个线程并发访问时,不用担心变量之间的冲突问题,避免了多线程之间的数据共享风险。


使用场景


ThreadLocal的使用场景主要在多线程环境中,能够为每个线程提供独立的变量副本。比如:


  • 用户上下文信息:比如,在 Web 应用中,每个请求可能由不同的线程处理,在处理用户请求时为每个线程维护独立的用户信息。

  • 数据库连接管理:比如,在多线程环境下,每个线程需要有自己独立的数据库连接。

  • 事务管理:比如,在处理事务时,每个线程可能需要有自己的事务上下文,确保线程安全的事务操作。

  • 数据传递:比如,同一个线程,在不同的方法之间传递数据,但又不想使用方法参数去传递,就可以使用 ThreadLocal。像我们常用的日志跟踪场景,跟踪的 ID 会存在 ThreadLocal 中贯穿整个链条。


总之有 2 个场景:


  1. 在多线程场景下,每个线程需要独立管理变量的场景。

  2. 某个线程想在整条链路上共享独立变量的场景。


使用方法


使用时,记住 3 条核心原则:


  • 每个线程都有一份独立的数据。

  • 线程内部使用的是 ThreadLocalMap 来保存数据,Key 就是 ThreadLocal 对象。

  • 使用完毕后,记得调用 remove 方法,防止内存溢出。


代码示例


独立保存变量的示例:


public class ThreadLocal4Independent {
private static ThreadLocal<Integer> threadLocalVar = new ThreadLocal<>();
public static void main(String[] args) { Runnable task = () -> { int num = (int) (Math.random() * 100); threadLocalVar.set(num); System.out.println("线程:" + Thread.currentThread().getName() + "的值:" + threadLocalVar.get()); threadLocalVar.remove(); };
new Thread(task, "1").start(); new Thread(task, "2").start(); }
}
复制代码


传递参数的示例:


public class ThreadLocal4DataPass {    // 使用ThreadLocal来存储需要在多个方法间传递的数据    private static final ThreadLocal<String> threadLocalData = new ThreadLocal<>();
public static void main(String[] args) { // 在主线程中设置数据 threadLocalData.set("ThreadLocal");
// 在主线程中调用不同的方法 method1(); method2(); // 清除ThreadLocal变量,防止内存泄露 threadLocalData.remove(); }
private static void method1() { // 在method1中获取数据并打印 String data = threadLocalData.get(); System.out.println("方法1拿到的数据是:" + data); }
private static void method2() { // 在method2中获取数据并打印 String data = threadLocalData.get(); System.out.println("方法2拿到的数据是:" + data); }}
复制代码


聊完使用场景和方法,接下来问面试官几个问题。


问题 1:请画出 ThreadLocal 和 Thread 的关系图


ThreadLocal 和 Thread 的关系图如下。



这里要牢记 3 点


1、数据实际上是存在 ThreadLocalMap 中的,ThreadLocalMap 归 Thread 所持有。见源代码。



2、ThreadLocalMap 内部使用的是 K-V 结构,Key 是我们定义的 ThreadLocal 对象。见源代码。



3、ThreadLocalMap 对 ThreadLocal 是弱引用关系。见源代码。



问题 2:为什么 ThreadLocalMap 里的 Key 是弱引用


那为什么 ThreadLocalMap 里的 Key 是使用 ThreadLocal 呢?为什么又是弱引用呢?


这就不得不说 JDK 的设计者的思想非常精妙了,有 3 点妙处:


  1. 一个线程要是存了多种数据,总得有个规则去找他们,那就根据定义的 ThreadLocal 对象去找吧。

  2. 对于开发者来说,他只需要使用 ThreadLocal 去保存数据即可,无需关系底层结构。也就是说对外暴露简单的使用方式即可,对于不需要调用方知道的细节全部隐藏。

  3. 一般情况下 Thread 的生命周期会很长,比如 Web 容器启动后,就会启动大量的线程丢到线程池中复用。所以 ThreadLocalMap 的生命周期也会很长。但是,ThreadLocal 对象存在的周期不一定长,如果,ThreadLocalMap 的 Key 对 ThreadLocal 是强引用的话,那么 ThreadLocal 对象就会一直存在于内存中得不到释放,最终会导致内存溢出,所以采用了弱引用。


问题 3:为什么 ThreadLocal 使用不当会造成内存溢出


从上图的关系图可以看出,Value 的生命周期是跟着 Thread 的生命周期来的,如果一直不处理的话,也会出现内存溢出的情况。


为了避免内存溢出的情况,我们在使用完 ThreadLocal 后,要即使调用 remove 方法,以便 JVM 回收 Value。



总结


ThreadLocal 是并发编程中的强大工具,能够为每个线程提供独立的变量副本,避免线程安全问题。并且这个ThreadLocal存入的值能够贯穿整个流程。使用时要注意上文的几点,防止造成内存溢出。


文章转载自:程序员半支烟

原文链接:https://www.cnblogs.com/mangod/p/18426330

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
反问面试官3个ThreadLocal的问题_Java_快乐非自愿限量之名_InfoQ写作社区