ThreadLocal源码解析

本文详细解释了ThreadLocal、ThreadLocalMap之间的关系,强调ThreadLocalMap如何存储每个线程独有的变量,以及get、set、remove方法的工作原理。同时讨论了哈希表设计、扩容策略和引用级别的内存管理,特别是弱引用在ThreadLocal中的作用。
摘要由CSDN通过智能技术生成

方法

三个主要方法:get set remove
讲三个方法前,现需要知道Thread,ThreadLocal,ThreadLocalMap三个之间的关系,首先ThreadLocalMap虽然是ThreadLocal中定义的静态内部类,但实际的 ThreadLocalMap 实例是作为 Thread 对象的一个字段存在的。这样设计的目的是允许每个线程存储自己独有的线程局部变量,而这些变量通过各自的 ThreadLocal 对象来访问。
ThreadLocalMap中key为ThreadLocal对象本身,value为该线程存储的变量的值。

get、set方法:ThreadLocal 会通过当前线程(Thread.currentThread())来访问或修改与当前线程关联的 ThreadLocalMap
image.png
image.png
remove方法:调用ThreadLocalMap的remove方法,清除对应的元素之后,调用expungeStaleEntry方法(从当前位置开始遍历,直到遇到空槽结束;这期间1.清理垃圾;2.将错位的元素放到离理想位置最近的地方,使Entry数组更紧凑)。
image.png
image.png

ThreadLocalMap

  1. 初始容量必须是2的幂

  2. 高效:与运算比取模运算更快

  3. 均匀分布:好的哈希函数会生成均匀分布的哈希值,这意味着低位的组合也将均匀分布。因此,使用哈希值的低位可以帮助实现均匀地将元素分布到哈希表的各个位置,减少冲突。

  4. 简化扩容:如果容量是2的幂,那么扩容后要么在原位置,要么移动到新位置(在原位置+原容量,只需要设置一个新的位),这使得在扩容时重新分配元素变得非常简单和高效;如果容量不是2的幂,元素在新哈希表中的位置不能简单通过保留原哈希值的低位来确定,就需要对每个值进行重新应用完整的哈希函数来确定扩容后的新位置。

例子:
假设有一个哈希值 101100(44 十进制)和一个容量为 16(10000 二进制)的哈希表,计算索引的操作如下:
101100& 01111
------

结果 00100

  1. 扩容时,使用较低的阈值以避免滞后(ThreadLocalMap 0.75)

为什么不能满阈值之后再扩容?
在数据结构接近其容量阈值时,性能可能会开始下降。对于哈希表而言,这可能表现为更多的哈希冲突和更长的查找时间。对于动态数组,接近容量限制可能导致频繁的内存重新分配。通过在达到较低阈值时提前扩容,可以减少这些操作的性能影响,从而“避免滞后”。

  1. 内部有一个Entry静态内部类(键值对实体的存储结构),继承WeakReference

image.png

当一个 ThreadLocal 对象被垃圾回收器回收时,由于 ThreadLocalMap.Entry 对 ThreadLocal 对象的引用是通过弱引用实现的,弱引用所指向的 ThreadLocal 对象确实可以被回收,这将使得 Entry 中的键(key)变为 null。然而,这个过程不会自动将 Entry 中的值(value)设置为 null。
在 ThreadLocalMap 的内部逻辑中,只有当对 ThreadLocalMap 进行操作(机制是懒惰的,只有调用 get()、set() 或 remove() 方法调用)时,相关的清理操作(例如 expungeStaleEntries())才会被触发。这些清理操作会检查键(key)是否为 null,如果是,则会清理掉这些 Entry,包括其中的值(value)。在清理操作之前,这些值(value)仍然占用内存,尽管它们的 ThreadLocal 引用已经被回收。
关键:键是对 ThreadLocal 对象的弱引用,value是被 ThreadLocalMap 的 Entry 以强引用的形式存储的。如果不手动操作,value不会被垃圾回收器回收。

概念补充

理想位置:指该ThreadLocal对象初次计算出的哈希值
错位:哈希值会出现冲突,造成“错位”元素的出现

四种引用级别:

  1. 强引用(Strong Reference)
    特点:强引用是最普遍的引用类型。如果一个对象具有强引用,垃圾收集器绝不会回收它。当内存空间不足时,Java 运行时宁愿抛出 OutOfMemoryError,也不会回收这种对象。
    用途:日常编程中默认使用的引用类型。
    示例:Object obj = new Object(); 这里的 obj 就是一个对 Object 实例的强引用。
  2. 软引用(Soft Reference)
    特点:软引用是用来描述一些还有用但非必需的对象。在系统将要发生内存溢出之前,将会把这些对象列进回收范围进行二次回收。如果回收后内存还是不足,才会抛出内存溢出异常。
    用途:适合用于实现内存敏感的缓存。
    示例:SoftReference softReference = new SoftReference<>(new Object());
  3. 弱引用(Weak Reference)
    特点:弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存空间足够与否,都会回收只被弱引用关联的对象。
    用途:弱引用同样适合于实现缓存,也用于 ThreadLocal 来避免内存泄漏。
    示例:WeakReference weakReference = new WeakReference<>(new Object());
  4. 虚引用(Phantom Reference)
    特点:虚引用是所有引用类型中最弱的一个。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。唯一的用途是这个对象被收集器回收时收到一个系统通知。
    用途:虚引用主要用来跟踪对象被垃圾回收的活动。
    示例:PhantomReference phantomReference = new PhantomReference<>(new Object(), referenceQueue); 其中 referenceQueue 是在对象被垃圾回收时接收通知的队列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值