1. ThreadLocal概念
ThreadLocal:线程变量,ThreadLocal中填充的变量属于当前线程。用于线程间的数据隔离。
每个Thread有一个ThreadLocalMap
在这个Thread里定义若干个ThreadLocal对象,用来保存线程数据。每个ThreadLocal对象使用set方法创建一个Entry :<ThreadLocal,Value>
这些Entry都保存到了这个线程持有的ThreadLocalMap里
如图:
2. ThreadLocal内存泄露
Java中的引用类型:
引用:指向内存地址
1.强引用(StrongReference)
使用 new 关键字创建的对象,不会被回收。程序代码中普遍存在的引用赋值。
当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用(SoftReference)
在内存不足时会被回收,适用于 缓存。
3.弱引用(WeakReference)
不论内存是否足够,只要发生 GC,弱引用的对象都会被回收。
4.虚引用(PhantomReference)
虚引用并不会决定对象的生命周期,如同没有引用一般,虚引用主要用来跟踪对象被垃圾回收的活动。
程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
内存泄露原因
Entry<ThreadLocal(key), Value>中
Key弱引用->ThreadLocal对象
Value强引用->要保存的线程局部变量。
线程经常使用池化思想也就是线程池管理(避免了频繁线程创建和销毁带来的开销),但这也就意味着线程长时间不会被释放,如果线程一直在运行,虽然Key被GC回收了,但是 Value 一直指向某个强引用对象,那么这个对象就不会被GC回收,从而导致内存泄漏。
解决方法:
使用完ThreadLocal后及时使用remove()
释放内存空间,remove()
方法会将当前线程的 ThreadLocalMap 中的所有 key 为 null 的 Entry 全部清除,这样就能避免内存泄漏问题。
3. ThreadLocal线程之间传递
父线程调用了子线程,那么子线程就有可能需要使用父线程中的数据,包括ThreadLocal
- InheritableThreadLocal 继承自
ThreadLocal
原理是每个线程维护两个ThreadLocalMap
普通 ThreadLocal 变量存储在 threadLocals 中,不会被子线程继承。
InheritableThreadLocal 变量存储在 inheritableThreadLocals 中,当 new Thread()
创建一个子线程时,Thread 的 init()
方法会检查父线程是否有 inheritableThreadLocals,如果有,就会拷贝 InheritableThreadLocal 变量到子线程。
TransmittableThreadLocal
阿里巴巴开源的工具类,继承并加强了InheritableThreadLocal
类
通过 装饰器模式 在原有的功能上做增强,以此来实现线程池场景下的 ThreadLocal
值传递。
改造的地方有两处:
-
实现自定义的
Thread
,在run()
方法内部做ThreadLocal
变量的赋值操作。 -
基于 线程池 进行装饰,在
execute()
方法中,不提交 JDK 内部的Thread
,而是提交自定义的Thread
4. ThreadLocal适用场景
- 在Web应用中存储用户Session
- 在数据库操作中存储数据库连接对象,避免多个线程共用一个连接导致事务混乱
- 用于跨方法、跨类时传递上下文数
缺点:
分布式系统不适用
父子线程传递问题
ThreadLocal
数据无法持久化