type
status
date
slug
summary
tags
category
icon
password
ThreadLocal
ThreadLocal 底层实现
ThreadLocal 是 Java 中所提供的线程本地存储机制,可以利用该机制将数据 缓存在某个线程内部 ,该线程可以在任意时刻、任意方法中获取缓存的数据。
ThreadLocal 底层是通过 ThreadLocalMap 来实现的,每个 Thread 对象中都存在一个 ThreadLocalMap 变量,这个 Map 的 key 就是我们要操作的 ThreadLocal 对象,Map 的 value 为需要缓存的值。我们在使用
ThreadLocal 的时候其实就是拿到当前线程中的 ThreadLocalMap 中的 key 和 value,ThreadLocal 本身不过是个工具类罢了。注意事项
如果在线程池中使用
ThreadLocal 可能会造成内存泄漏,因为当 ThreadLocal 对象使用完之后,应该要把设置的 key 和 value——也就是 Entry 对象进行回收。但线程池中的线程不会回收,并且 Thread 对象是通过强引用指向 ThreadLocalMap,ThreadLocalMap 也是通过强引用指向 Entry 对象,线程不被回收,Entry 对象也就不会被回收,从而导致不再使用的 Entry 对象越来越多,出现内存泄漏。解决办法是,在使用了
ThreadLocal 对象之后,手动调用 ThreadLocal 的 remove() 方法,手动清除 Entry 对象。
实线箭头是强引用,虚线箭头是弱引用
Q:什么是强引用?A:强引用是 Java 中最常见的引用类型。普通的对象引用就是强引用。例如:在这个例子中,obj就是一个强引用,它指向了一个对象。如果一个对象具有强引用,即使内存紧张,垃圾回收器也不会回收这个对象,直到这个对象不再有任何强引用指向它。
^4efc86
Q:什么是弱引用?A:你可以使用java.lang.ref.WeakReference类来创建弱引用:它与强引用的主要区别在于垃圾回收器的处理方式。如果一个对象只有弱引用指向它,在下一次垃圾回收时,无论内存是否充足,垃圾回收器都会回收该对象。
Q:为啥value值不跟key一样用弱引用?A:
key使用弱引用:正常情况下,在线程执行过程中,Thread本身通常会被其他地方(如线程池、任务队列、栈等)持有强引用,它不会轻易被回收。当线程执行结束且不再有强引用时,Thread对象连同其持有的ThreadLocalMap都会被垃圾回收。由于ThreadLocalMap的key是对ThreadLocal的弱引用,所以只要ThreadLocal没有其他强引用,它也会被 GC 回收。整个entry(包括value)也会随Thread的回收一起被清理。只有在线程池中,线程被复用而不被回收,才会留下一个null的key和还活着的value。
value使用强引用:当一个线程创建并开始执行时,它通常会使用自己的局部变量(即ThreadLocal中的value),尤其是 web 服务中,这个局部变量应该存在直到线程结束或者完成其任务。使用强引用可以确保局部变量的持久性,直到线程结束或线程池的线程回收。
- 如果将
value设置为弱引用,垃圾回收可能会使其还被线程使用时就被回收掉,这将导致线程内部逻辑发生错误。
总结:当你在一个方法/栈中创建
ThreadLocal,该对象就被栈强引用着,直到方法执行完毕,栈被销毁,失去了强引用的、只有 ThreadLocalMap.Entry 弱引用的 ThreadLocal 就会被 GC 回收,但 ThreadLocalMap.Entry 中的 value 却实打实强引用了一个对象,entry 不死它就不死,所以说养成良好习惯 new ThreadLocal() 和 ThreadLocal.remove() 要在方法中成对出现。应用场景
当一个变量是共享的(成员变量、static 等),但是需要每个线程互不影响,相互隔离,就可以使用 ThreadLocal。
- 跨层传递信息的时候,每个方法都声明一个参数很麻烦,A、B、C、D 4 个类互相传递,每个方法都声明参数降低了维护性,可以用一个 ThreadLocal 共享变量,在 A 存值, B、C、D 都可以获取。
- 隔离线程,存储一些线程不安全的工具对象,如(SimpleDateFormat)。
- Spring 中的事务管理器就是使用的 ThreadLocal,这也是为什么声明式事务如果用到了多线程,它是没有办法达到一致性的,因为一个线程就存储了一个事务。
- SpringMVC 的 HttpSession、HttpServletReuqest、HttpServletResponse 都是放在 ThreadLocal,因为 servlet 是单例的,而 SpringMVC 允许在 controller 类中通过@Autowired 配置 request、response 以及 requestcontexts 等实例对象,在任何地方获取请求都永远是当前的请求。底层就是搭配 ThreadLocal 才实现线程安全。
- 作者:林明菁
- 链接:https://blog.lxuan.fun/article/ThreadLocal
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章






