如何理解ThreadLocal源码以及其存在的问题

如果有人直接问起,让你介绍一下ThreadLocal或者讲一讲自己对于ThreadLocal的理解,突然之间会感觉有点无从下手,所以想总结一下,写一个该如何回答这个问题的文字版。

首先,是什么

摘抄一些来自其他博客的描述

ThreadLocal提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal变量通常被private static修饰。当一个线程结束时,它所使用的所有ThreadLocal相对的实例副本都可被回收。

ThreadLocal适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

简单点,转换成自己的话来说

ThreadLocal,顾名思义,线程本地变量,存放在线程对象中,用于在不同线程中隔离数据。

其次,使用场景

由于其特性,可以用于管理一些需要在完整线程生命周期内存在,且线程间需要隔离的数据或者资源。

1 隐式参数传递

session或者在filter中处理过后获得的用户权限信息,在spring里一个请求对应一个线程,所以可以把这些信息作为上下文放到ThreadLocal里来向下传递,不需要写在每个方法的参数里来显式传递

2 资源管理

例如spring的声明式事务,需要保证获取到的connection是同一个,才能实现事务,这时候也是通过ThreadLocal来管理connection的。

但是,ThrealLocal不是用来解决线程安全问题的。因为ThreadLocal特点是同一数据的多线程副本,并不解决多线程共享变量的问题

接下来,实现原理

从结构上来看,每个线程对象持有一个ThreadLocalMap,这个map底层是一个Entry数组,Entry继承自WeakReference,持有一个弱引用对象和value。ThreadLocalMap以ThreadLocal对象作为key,类似hashmap,通过hash值确定存储位置,不过与hashmap等集合中的map不同的是,使用的解决hash冲突的方式不同

这里主要是要注意到ThreadLocal与其他Map不同的两个地方

  • 1 是Entry的实现,并不是直接指定key,value;而是用继承自WeakReference的reference和自己定义的value。弱引用的特点之后再讲。

  • 2 是数据结构不同,根本原因可能是因为数据量上的差异,导致选择的解决hash冲突算法不同,表现出来的特征就变成了数据结构的差异和操作逻辑上的不同。

弱引用的作用,弱引用的特点是当一个对象只被弱引用指向的时候,会被当做垃圾回收,如果还有其他强引用指向的话就不会。这个特点其实就是为某些对象增加一个可用的引用而不因为这个引用而延长对象的生命周期。

具体到ThreadLocal里就是,ThreadLocalMap是一个跟随线程生命周期的对象,而里每个Entry的key都是指向了可能是局部变量的ThrealLocal对象,正常情况下ThreadLocal对象在生命周期结束后会被垃圾收集器回收,如果在ThreadLocalMap中还有对它的强引用,根据垃圾收集算法,这时ThreadLocal对象是不会被回收的。所以使用弱引用,当ThrealLocal对象生命周期结束后,即使还在ThreadLocal中,没被删除,也会被垃圾收集器清理。

基本操作的大致流程

出于好奇,去看了看ThreadLocal里一些主要方法的源码,虽然还是有一些细节没能理解,但是总算是摸清楚了大致的流程。也画了几张图记录下来。

set方法

set

get方法

get

remove方法

remove

内存泄漏问题

虽然使用弱引用保证了ThreadLocal对象能被回收,不会因为有ThreadLocalMap的指向而不会被垃圾回收。但是当key指向的对象被回收,key值为null之后,value所指向的对象无法被访问到,也会出现内存泄漏的问题。

ThreadLocal在实现的时候考虑了这个问题,所以在get set remove方法里都添加了清除key为null数据的操作。

不过即使这些方法里有清除无效数据的操作还是会有问题,网上的一些博客里讲内存泄漏只会发生在没有手动remove,之后也不再进行set get remove操作的情况下。如果真是这样,那么发生内存泄漏的概率是很低的,但是我去看了眼get的代码,发现并不是这样。

因为清除无效数据是有选择性的

简单看下代码就能发现,在ThreadLocal的get方法中,清除key为null对应value数据的情况只在miss的情况下发生,而如果内部保存的数据之间不存在hash冲突,就不会进入getEntryAfterMiss方法里去清除无效数据。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

set方法的也是类似的,触发清除条件也是有一定限制的,set的过程中也只会在有hash冲突的情况下去查看key是否为null,另外会在set结束后,根据size来判断是否需要扩容时也进行清除。流程是会先通过位运算找一小部分slot进行判断是否需要清理。随机清理没有找到,且当前的长度大于了阈值限制的话,才会进入全量扫描寻找是否有能够清理的数据;最终,在size超过阈值四分之一以后会进行resize操作。

remove方法,从hash的节点开始遍历,先删除引用指向,然后再清除单个节点的value和Entry,用的方法和getEntryAfterMiss里一样,然后会在进行rehash操作来保证删除节点之后的那些节点不会因为当前节点的删除而出现不可达的情况。

由于清理无效数据的各种限制,出现存在无效数据而导致的内存泄露情况是很有可能发生的,所以为了避免出现内存泄漏,还是每次使用过后手动remove比较好。

到这就是我目前对于ThreadLocal的全部理解了,以此当作对自己技术水平的记录。

FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值