为什么Netty要造FastThreadLocal?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Netty 的 FastThreadLocal 是一种高效的线程局部变量,设计用于解决标准 ThreadLocal 的性能和内存泄漏问题。FastThreadLocal 通过使用数组而非哈希表存储数据,避免了哈希冲突带来的性能损耗,查询效率达到 O(1)。此外,FastThreadLocal 提供了 remove() 方法和 FastThreadLocalRunnable 类,以防止内存泄漏,确保在执行完成后自动清理对象。相比于 ThreadLocal,FastThreadLocal 具有更高的性能和安全性。

FastThreadLocal 从字面意义上来看,它是“Fast”+“ThreadLocal”的结合体,寓意为快速的 ThreadLocal。那么,问题来了,Netty 为什么要再造一个 FastThreadLocal?FastThreadLocal 运行快的原因是啥?除了快之外,它还有其他优势吗?

1.先从ThreadLocal说起

ThreadLocal 线程本地变量,每个线程都拥有一份该变量的独立副本,即使是在多线程环境下,每个线程也只能修改和访问自己的那份副本,从而避免了线程安全问题,实现了线程间的隔离。

ThreadLocal 底层是使用 ThreadLocalMap 实现的,这点从 JDK 的源码中可以看出,核心源码如下:

java

复制代码

private void set(Thread t, T value) {
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

从 ThreadLocal 的 set 方法可以看出,ThreadLocal 是存储在 ThreadLocalMap 中的,咱们继续看 ThreadLocalMap 的源码实现:从上面源码可以看出,ThreadLocalMap 中存放的是 Entry(哈希桶),而 Entry 中的 key 就是 ThreadLocal,而 value 则是要存储的值,所以我们得出 ThreadLocal 的底层实现如下:

2.ThreadLocal存在的问题

2.1 性能问题

因为 ThreadLocal 底层是使用 ThreadLocalMap 实现的,ThreadLocalMap 类似于哈希表。当一个线程是拥有多个 ThreadLocal 时,ThreadLocalMap 很容易发生 Hash 冲突,此时 ThreadLocal 就不得不使用线性探测法来解决哈希冲突了,而在解决 Hash 冲突时需要不停地向下寻找,效率较低,因此 ThreadLocal 存在的第一个问题就是性能较低。

2.2 内存泄漏问题

ThreadLocal 也存在内存泄漏的问题,具体来说 ThreadLocalMap 使用 ThreadLocal 对象作为键(Key),并且这个键是弱引用(WeakReference)类型。这意味着当没有其他强引用指向 ThreadLocal 对象时,它将会在下次垃圾回收时被回收。然而,Entry 中保存的值(Value)仍然是强引用,这就可能导致以下问题:

  1. 弱引用键的回收:一旦外部对 ThreadLocal 实例的所有强引用消失,ThreadLocal 对象本身就会变为弱可达状态。在下一次垃圾回收时,由于是弱引用,ThreadLocal 对象会被回收,但 Entry 中的 Value(即实际存储的数据)仍然是强引用,因此不会被回收。
  2. Map 引用陷阱:即使 ThreadLocal 键被回收,Entry 仍然存在于 ThreadLocalMap 中,并且由于 Map 对 Entry 的引用,这些 Entry 所持有的 Value 对象也不会被垃圾回收,从而导致这些对象无法被使用也无法被释放,形成了所谓的“内存泄漏”。
  3. 线程长期存活:在一些场景下,特别是使用线程池时,线程的生命周期往往很长,甚至伴随整个应用的生命周期。这意味着 ThreadLocalMap 中的 Entry 可能会长时间不被清理,进一步加剧了内存泄漏问题。

所以,综合来看,在使用 ThreadLocal 时,如果在使用完之后,未及时调用 remove() 方法的话,就会出现内存泄漏的问题。

3.FastThreadLocal特点

为了解决 ThreadLocal 存在的这些问题,所以 Netty 创造出了一个 FastThreadLocal,FastThreadLocal 的特点如下。

3.1 效率高

FastThreadLocal 之所以性能高的原因是因为其存储结构,在 FastThreadLocal 中并没有向 ThreadLocal 那样,使用哈希表来存储元素,而是使用了数组来进行元素存储,它的核心实现源码如下:

java

复制代码

public class FastThreadLocal<V> {
    // FastThreadLocal中的index是记录了该它维护的数据应该存储的位置
    // InternalThreadLocalMap数组中的下标, 它是在构造函数中确定的
    private final int index;
 
    public InternalThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }
    // 省略其他代码
}

FastThreadLocal 核心类 InternalThreadLocalMap 的实现源码如下:

java

复制代码

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    // 自增索引, ⽤于计算下次存储到Object数组中的位置
    private static final AtomicInteger nextIndex = new AtomicInteger();
 
    private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8;
 
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
            nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }
    // 省略其他代码
}

从上述源码可以看出,FastThreadLocal 在初始化的时候分配一个数组索引 index,index 的值采用原子类 AtomicInteger 保证顺序递增,通过调用 InternalThreadLocalMap.nextVariableIndex() 方法获得。然后在读写数据的时候通过数组下标 index 直接定位到 FastThreadLocal 的位置,时间复杂度为 O(1)。如果数组下标递增到非常大,那么数组也会比较大,所以 FastThreadLocal 是通过空间换时间的思想提升读写性能。

因此,在 FastThreadLocal 中并不需要使用线性探测法来解决 Hash 冲突,因为它是使用数组进行存储的,每次使用下标进行查询即可,它的查询时间复杂度也是 O(1) 的,所以它的操作效率很高。

3.2 安全性更高

JDK 原生的 ThreadLocal 使用不当可能造成内存泄漏,只能等待线程销毁。然而 FastThreadLocal 却不存在这个问题,在 FastThreadLocal 中不仅提供了 remove() 方法可以主动清除对象,而且它还封装了 FastThreadLocalRunnable,FastThreadLocalRunnable 在最后使用完之后会自动调用 removeAll() 方法将集合中所有对象清理掉,因此 FastThreadLocal 更安全。

FastThreadLocalRunnable 自动清除对象的实现核心源码如下:

java

复制代码

final class FastThreadLocalRunnable implements Runnable {
    private final Runnable runnable;
    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            FastThreadLocal.removeAll();
        }
    }
    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable 
                ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

4.小结

FastThreadLocal 相比于 ThreadLocal 存在以下两个主要优点:

  1. 性能更高:FastThreadLocal 使用了数组的方式来存储元素,所以它的查询时间复杂度 O(1) 相比于 ThreadLocal 的哈希表操作效率更高。
  2. 安全性更高:FastThreadLocal 中的 FastThreadLocalRunnable 在最后执行完之后会自动调用 removeAll() 将集合中所有对象都清理掉,可以避免内存泄漏的问题,所以它的安全性更高。


转载来源:https://juejin.cn/post/7373623949836779554

相关文章
|
5月前
|
存储 设计模式 消息中间件
京东二面:为什么Netty要造FastThreadLocal?
FastThreadLocal 从字面意义上来看,它是“Fast”+“ThreadLocal”的结合体,寓意为快速的 ThreadLocal。那么,问题来了,Netty 为什么要再造一个 FastThreadLocal?FastThreadLocal 运行快的原因是啥?除了快之外,它还有其他优势吗? ## 1.先从ThreadLocal说起 ThreadLocal 线程本地变量,每个线程都拥有一份该变量的独立副本,即使是在多线程环境下,每个线程也只能修改和访问自己的那份副本,从而避免了线程安全问题,实现了线程间的隔离。 ThreadLocal 底层是使用 ThreadLocalMap 实现
41 0
京东二面:为什么Netty要造FastThreadLocal?
|
存储 Java
netty系列之:给ThreadLocal插上梦想的翅膀,详解FastThreadLocal
JDK中的ThreadLocal可以通过get方法来获得跟当前线程绑定的值。而这些值是存储在ThreadLocal.ThreadLocalMap中的。而在ThreadLocalMap中底层的数据存储是一个Entry数组中的。 那么从ThreadLocalMap中获取数据的速度如何呢?速度有没有可以优化的空间呢? 一起来看看。
|
存储 Java API
窥探 Netty 源码!FastThreadLocal 究竟快在哪里?
本文涉及到的 Netty 源码版本为 4.1.6。
215 0
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13473 1
|
5月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
121 1
|
10月前
|
NoSQL Java Redis
跟着源码学IM(十二):基于Netty打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。 原本打算做个多人斗地主练习程序,但那需要织入过多的业务逻辑,因此一方面会带来不必要的理解难度,让案例更为复杂化,另一方面代码量也会偏多,所以最终依旧选择实现基本的IM聊天程序,既简单,又能加深对Netty的理解。
150 1
|
5月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
119 0
|
5月前
|
编解码 安全 前端开发
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
205 0
|
分布式计算 网络协议 前端开发
【Netty底层数据交互源码】
【Netty底层数据交互源码】
|
Java 容器
【深入研究NIO与Netty线程模型的源码】
【深入研究NIO与Netty线程模型的源码】