type
status
date
slug
summary
tags
category
icon
password
ThreadLocal

ThreadLocal 底层实现

ThreadLocal 是 Java 中所提供的线程本地存储机制,可以利用该机制将数据 缓存在某个线程内部 ,该线程可以在任意时刻、任意方法中获取缓存的数据。
notion image
ThreadLocal 底层是通过 ThreadLocalMap 来实现的,每个 Thread 对象中都存在一个 ThreadLocalMap 变量,这个 Map 的 key 就是我们要操作的 ThreadLocal 对象,Map 的 value 为需要缓存的值。
我们在使用 ThreadLocal 的时候其实就是拿到当前线程中的 ThreadLocalMap 中的 key 和 value,ThreadLocal 本身不过是个工具类罢了。

注意事项

如果在线程池中使用 ThreadLocal 会造成内存泄漏,因为当 ThreadLocal 对象使用完之后,应该要把设置的 key 和 value——也就是 Entry 对象进行回收。但线程池中的线程不会回收,并且线程对象是通过强引用指向 ThreadLocalMap,ThreadLocalMap 也是通过强引用指向 Entry) 对象,线程不被回收,Entry 对象也就不会被回收,从而导致不再使用的 Entry 对象越来越多,出现内存泄漏。
解决办法是,在使用了 ThreadLocal 对象之后,手动调用 ThreadLocal 的 remove 方法,手动清除 Entry 对象。
notion image
Q:什么是强引用?
A:强引用是 Java 中最常见的引用类型。普通的对象引用就是强引用。例如:
在这个例子中,obj 就是一个强引用,它指向了一个对象。如果一个对象具有强引用,即使内存紧张,垃圾回收器也不会回收这个对象,直到这个对象不再有任何强引用指向它。
Q:什么是弱引用?
A:你可以使用 java.lang.ref.WeakReference 类来创建弱引用:
它与强引用的主要区别在于垃圾回收器的处理方式。如果一个对象只有弱引用指向它,在下一次垃圾回收时,无论内存是否充足,垃圾回收器都会回收该对象。
Q:为啥 value 值不跟 key 一样用弱引用?
A:
  • key 使用弱引用:正常情况下,在线程执行过程中,线程对象本身通常会被其他地方(如线程池、任务队列、栈等)持有强引用,它不会轻易被回收。当线程执行结束后,线程对象不再活跃也不再有强引用,此时线程对象就会被回收,key 这个 " 快捷方式 " 也被回收。由于 ThreadLocalMap 中的是基于线程的生命周期来管理的,当线程对象被回收时,整个的 entry 就会被回收,顺带着其中的 value。只有线程池中线程复用的情况下,才会让线程不被回收,进而导致失去了 keyentry 仍然存活。
  • value使用强引用:当一个线程创建并开始执行时,它通常会使用自己的局部变量(即 ThreadLocal 中的 value),尤其是 web 服务中,这个局部变量应该存在直到线程结束或者完成其任务。使用强引用可以确保局部变量的持久性,直到线程结束或线程池的线程回收。
    • 如果将 value 设置为弱引用,垃圾回收可能会在 ThreadLocalMapvalue 还被线程使用时就回收掉这个值,这将导致线程内部逻辑发生错误。
Q:线程池中线程复用的情况下,key 是不是也不会失去引用了?因为线程池一直强引用着线程。
A:是的,在线程池中,线程对象通常会被线程池强引用。因此,即使 ThreadLocalMap 中的 key(线程对象)是弱引用,它也不会在执行过程中被垃圾回收,因为线程池始终持有对该线程的强引用。这也就意味着,ThreadLocalMap 中的条目(keyvalue)会继续存在,直到线程池中某个线程完全不再使用(例如:线程池关闭或线程对象被清除)。

应用场景

当一个变量是共享的(成员变量、static 等),但是需要每个线程互不影响,相互隔离,就可以使用 ThreadLocal。
  1. 跨层传递信息的时候,每个方法都声明一个参数很麻烦,A、B、C、D 4 个类互相传递,每个方法都声明参数降低了维护性,可以用一个 ThreadLocal 共享变量,在 A 存值, B、C、D 都可以获取。
  1. 隔离线程,存储一些线程不安全的工具对象,如(SimpleDateFormat)。
  1. Spring 中的事务管理器就是使用的 ThreadLocal,这也是为什么声明式事务如果用到了多线程,它是没有办法达到一致性的,因为一个线程就存储了一个事务。
  1. SpringMVC 的 HttpSession、HttpServletReuqest、HttpServletResponse 都是放在 ThreadLocal,因为 servlet 是单例的,而 SpringMVC 允许在 controller 类中通过@Autowired 配置 request、response 以及 requestcontexts 等实例对象,在任何地方获取请求都永远是当前的请求。底层就是搭配 ThreadLocal 才实现线程安全。
浅谈一下HashMapJWT
Loading...