本篇咱们聊下JDK中的ThreadLocal具体实现和Netty中FastThreadLocal所做的优化。
版权声明:本图来自CSDN博主「半夏_2021」的原创文章。
原文链接:https://blog.csdn.net/fd2025/article/details/120019239
假设现在已经个Thread对象, 我们要给这个对象关联一个属性, 你会怎么做? 显然直接给加个属性即可。如果是增加多个属性, 则增加一个集合, 后续的操作都是基于集合的操作。JDK的小伙伴也是这么实现的。
Entry本身继承了WeakReference, 也就意味着Entry本身不会影响GC对ThreadLocal对象的垃圾回收, 更直接说就是被ThreadLocal对象在GC层面做可达性统计时不考虑Entry的引用, 而Entry本身又增加了value字段用于存储对应的value。显然, 对entry的预期是当entry.getKey() == null时, 意味着key已经被回收, 此时应该将entry释放掉。
在ThreadLocalMap中, 通过数组存储Entry。此外需要解决两个问题。一个是如何处理hash冲突, 二是如何进行扩容。在ThreadLocalMap中hash冲突通过开放地址法解决, 还有一种方案是拉链法。前者在数据量少, hash冲突比较低时使用, 后者则在存储数据比较多时使用(比如HashMap)。扩容则根据负载因子(一般是0.75), 容量达到这个比例就扩容。
实际情况下, ThreadLocalMap中的ThreadLocal对象比较少, 并且考虑到正确的使用姿势应该是使用完毕即remove因此空间是复用的, 因此缩容的意义不大。
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap–>Entry–>Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。
JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
版权声明:本部分来自CSDN博主「半夏_2021」的原创文章。
原文链接:https://blog.csdn.net/fd2025/article/details/120019239
Netty作为一个深度优化的网络框架, 你能看到其线程上下文都围绕EventLoop展开, 也就是围绕单个Thread展开, 因此必定是一个ThreadLocal的使用大户。鉴于网络场景下对ThreadLocal的使用是一个高频动作, 因此对其优化是值得的。首先ThreadLocal本身已经是基于数组存储的,元素访问通过下标完成, 理论上已经是最快的数据结构了。但美中不足就是当hash冲突的时候, 需要继续向后探测, 并且在读取和写入时需要做整理。Netty框架则想招把hash冲突这回事给避免了。
把InternalThreadLocalMap对象放在ThreadLocal中, 进而关联到普通的Thread上。操作上与常规ThreadLocal的区别如下:
常规操作: Thread -> ThreadLocalMap -> ThreadLocal -> #get() -> value;
新操作: Thread -> ThreadLocalMap -> ThreadLocal -> #get() -> InternalThreadLocalMap -> #get() -> value;
在Netty中定义为FastThreadLocalThread, 其中代理对ThreadLocalMap的操作到InternalThreadlLocalMap上, 这样才能真正体现出FastThreadLocal的性能优势。如果是普通Thread+FastThreadLocal, 不仅性能没有提升, 反而由于访问链路过长降低性能。
本篇咱们聊下JDK中的ThreadLocal具体实现和Netty中FastThreadLocal所做的优化, 可见Netty对细节的孜孜追求。其中对业务场景的深刻洞察和对所用技术优缺点的精准掌控值得我们学习。