ThreadLocal(线程变量)的底层原理

目录

ThreadLocal的原理

ThreadLocal方法

main()方法

set()方法

getMap()方法

Entry结构

ThreadLocal如何引起内存泄露

内存泄露

强引用(Strong)

软引用(Soft)

弱引用(Weak)

虚引用(Phantom)

总结


ThreadLocal的原理

ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,对这个共享变量的修改,通过无锁或者有锁的机制保证线程的安全

ThreadLocal是为每一个线程,创建一个只属于它自己的变量副本,线程可以改变自己所拥有的变量副本,而不会影响其他线程所对应的副本

往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的

一个线程使用自己的局部变量比使用全局变量方便且安全,因为局部变量只有线程自己能看见,不会影响其他线程

ThreadLocal方法

main()方法

set()方法

getMap()方法

threadLocals的类型为ThreadLocal.ThreadLocalMap

ThreadLocal.ThreadLocalMap由一个Entry数组组成

Entry结构

一个Entry是一个key-value类型的数据,key为ThreadLocal对象,value为set()中所传递的参数值

上图中:Thread强引用着ThreadLocalMap, ThreadLocalMap弱引用着ThreadLocal

set()的过程,先得到当前线程,每个线程都有一个对应的threadLocalMap集合(threadLocals,里面保存了和当前线程相关的所有ThreadLocal,维护了一个数组,数组里面的每个元素都是一个Entry对象)

ThreadLocal如何引起内存泄露

内存泄露

分配的堆内存由于各种原因未能释放或无法释放,造成系统内存的浪费

每种编程语言都有自己操作内存中元素的方式,在Java中是通过引用(reference)

在 Java中一切都被视为对象,我们操作的标识符,实际上是对象的一个引用,程序通过这个引用来实现操作对象

Java中的引用分为四种,从强到弱依次为∶强引用、软引用、弱引用和虚引用

引用的强度代表了对内存占用的能力大小,具体表现在GC的时候,引用的对象会不会被回收,什么时候被回收

强引用(Strong)

   只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收

   如果想中断强引用与对象之间的联系,可以主动将强引用赋值为null,这样一来,VM就可以在适当的时候回收对象

软引用(Soft)

   软引用是用来描述一些还有用但是并非必须的对象

   相对于强引用,软引用在内存充足时可能不会被回收,在内存不够时会被回收

   如果内存不足,系统在有可能发生内存溢出之前,将会把这些对象进行回收

   如果这次回收还没有足够的内存,才会抛出内存溢出

弱引用(Weak)

   弱引用的引用强度比软引用要更弱一些,表示非必须的对象

   无论内存是否足够,被弱引用关联的对象只能生存到下一次GC发生之前

   也就是说只要JVM开始进行垃圾回收,那些被弱引用关联的对象都会被回收

虚引用(Phantom)

   它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间造成影响

   为一个对象设置虚引用关联的唯一目的,就是在这个对象被GC回收时收到一个系统通知

从Entry的源码可以看出,ThreadLocalMap是以弱引用的方式引用者ThreadLocal,弱引用的对象只有触发了垃圾回收,就会被回收掉。

如果ThreadLocal没有被TreadLocalMap以外的对象引用,则在下一次GC的时候,ThreadLocal实例就会被回收

那么此时数组中的一个entry中的Key就是null,因此在没有额外操作的情况下,此处entry里面保存的Value永远不会被外部访问到

但是只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存

总结

ThreadLocal如果使用不当,有可能引起内存泄露

### ThreadLocal底层实现原理 ThreadLocalJava 提供的一种机制,允许创建线程局部变量。这些变量不同于普通的实例或静态变量,因为每个线程都有自己独立的一份副本。 #### 1. Thread 和 ThreadLocalMap 关系 每当一个新的 `Thread` 对象被创建时,会关联一个名为 `ThreadLocal.ThreadLocalMap` 的映射表。该映射表存储了当前线程所持有的所有 `ThreadLocal` 变量及其对应的值[^1]。 ```java public class Thread implements Runnable { /* Other code */ ThreadLocal.ThreadLocalMap threadLocals = null; } ``` #### 2. Entry 结构 `ThreadLocalMap` 使用一种特殊的哈希表结构来保存键值对,其中键是弱引用类型的 `ThreadLocal` 实例,而值则是强引用的对象。具体来说: - 键 (`key`):指向 `ThreadLocal` 对象的弱引用; - 值 (`value`):实际存储的数据; 这种设计使得当某个 `ThreadLocal` 不再被任何地方引用时,即使其所在的线程仍然存活,GC 也可以回收掉不再使用的 `ThreadLocal` 实例以及相应的条目,从而避免潜在的内存泄漏问题[^2]。 然而需要注意的是,如果开发者未能及时清除已过期或者不再需要的 `ThreadLocal` 条目,则仍有可能引发内存泄露风险[^3]。 #### 3. 子线程继承父线程中的 ThreadLocal 默认情况下,新启动的子线程并不会自动复制来自父线程的 `ThreadLocal` 数据。只有通过特定方式(如 `InheritableThreadLocal`),才能让子线程获得一份父线程中定义好的 `ThreadLocal` 初始状态拷贝[^4]。 ### 面试常见问题及答案 1. **什么是 ThreadLocal?** - ThreadLocal 类提供了线程本地 (thread-local) 变量的功能,即每个线程都有自己的专属副本,互不影响。 2. **为什么会出现内存泄漏?如何预防?** - 当 `ThreadLocal` 被设置为空(null),但是它的 entry 还存在于 map 中未移除的话,由于 key 是 weak reference, 所以会被垃圾收集器回收,但此时 value 却无法访问到,形成悬空指针,进而导致内存泄漏。可以通过显式调用 remove 方法释放资源来防止这种情况发生。 3. **ThreadLocal 如何解决多线程环境下的共享变量冲突?** - 每个线程都拥有自己单独的空间去存取数据,这样就有效地解决了多个线程操作同一对象所带来的同步问题。 4. **ThreadLocal 是否适用于所有的场景?举例说明不适合的情况。** - 并不是所有情况都适合使用 `ThreadLocal`。比如在线程池环境中,如果不小心处理好 `ThreadLocal` 的生命周期管理,很容易引起意想不到的行为甚至严重的性能瓶颈。 5. **ThreadLocal 和 InheritableThreadLocal 有什么区别?** - 主要差异在于后者可以让子线程继承自父线程的初始值设定,前者则不具备此特性,默认状态下两者都是各自维护一套完全隔离的状态空间。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值