ThreadLocal
TheadLocal有什么用?
ThreadLocal 类允许每个线程绑定自己的值,可以将其形象地比喻为一个“存放数据的盒子”。每个线程都有自己独立的盒子,用于存储私有数据,确保不同线程之间的数据互不干扰。
创建一个 ThreadLocal 变量时,每个访问该变量的线程都会拥有一个独立的副本。这也是 ThreadLocal 名称的由来。线程可以通过 get() 方法获取自己线程的本地副本,或通过 set() 方法修改该副本的值,从而避免了线程安全问题。
ThreadLocal 和 Synchronized 区别?
Synchronized 是基于锁机制的,用于控制对共享资源的访问,确保线程间数据的一致性和安全性,实现线程间的互斥访问。
所以说,Synchronized 是通过换空间让多个线程排队访问,ThreadLocal 是空间换时间为每个线程提供了一份变量的副本,从而实现线程隔离。
ThreadLocal原理
Thread类源代码。
1 | |
Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。
ThreadLocal类的set()方法
1 | |
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
1 | |
ThreadLocal 内存泄露问题是怎么导致的?
内存泄漏是由于无用的对象无法被GC回收,长时间占用内存,导致可用内存逐渐减少,最终形成内存溢出。
ThreadLocal 内存泄漏的根本原因在于其内部实现机制。
每个线程维护一个名为 ThreadLocalMap 的 map。 当你使用 ThreadLocal 存储值时,实际上是将值存储在当前线程的 ThreadLocalMap 中,其中 ThreadLocal 实例本身作为 key,而你要存储的值作为 value。
ThreadLocalMap 的 key 和 value 引用机制:
key 是弱引用:
ThreadLocalMap中的 key 是ThreadLocal的弱引用 (WeakReference<ThreadLocal<?>>)。 这意味着,如果ThreadLocal实例不再被任何强引用指向,垃圾回收器会在下次 GC 时回收该实例,导致ThreadLocalMap中对应的 key 变为null。value 是强引用:
ThreadLocalMap中的 value 是强引用。 即使 key 被回收(变为null),value 仍然存在于ThreadLocalMap中,被强引用,不会被回收。
当 ThreadLocal 实例失去强引用后,其对应的 value 仍然存在于 ThreadLocalMap 中,因为 Entry 对象强引用了它。如果线程持续存活(例如线程池中的线程),ThreadLocalMap 也会一直存在,导致 key 为 null 的 entry 无法被垃圾回收,机会造成内存泄漏。
内存泄漏的发生需要同时满足两个条件:
ThreadLocal实例不再被强引用;线程持续存活,导致
ThreadLocalMap长期存在。
ThreadLocalMap 如何解决 Hash 冲突
通过线性探测法,从头到尾去寻找下一个可用的位置,循环遍历。
如何跨线程传递ThreadLocal的值?
如果想要在异步场景下传递 ThreadLocal 值,有两种解决方案:
InheritableThreadLocal:InheritableThreadLocal是 JDK1.2 提供的工具,继承自ThreadLocal。使用InheritableThreadLocal时,会在创建子线程时,令子线程继承父线程中的ThreadLocal值,但是无法支持线程池场景下的ThreadLocal值传递。TransmittableThreadLocal:TransmittableThreadLocal(简称 TTL) 是阿里巴巴开源的工具类,继承并加强了InheritableThreadLocal类,可以在线程池的场景下支持ThreadLocal值传递。
InheritableThreadLocal 原理
在 Thread 类中添加了一个新的 ThreadLocalMap ,命名为 inheritableThreadLocals ,该变量用于存储需要跨线程传递的 ThreadLocal 值。
1 | |
通过改造 Thread 类的构造方法来实现,在创建 Thread 线程时,拿到父线程的 inheritableThreadLocals 变量赋值给子线程即可。
TransmittableThreadLocal 原理
阿里巴巴无法改动 JDK 的源码,因此他内部通过 装饰器模式 在原有的功能上做增强,以此来实现线程池场景下的 ThreadLocal 值传递。
TTL 改造的地方有两处:
实现自定义的
Thread,在run()方法内部做ThreadLocal变量的赋值操作。基于 线程池 进行装饰,在
execute()方法中,不提交 JDK 内部的Thread,而是提交自定义的Thread。