首页 > 其他分享 >京东二面:为什么Netty要造FastThreadLocal?

京东二面:为什么Netty要造FastThreadLocal?

时间:2024-05-28 14:22:27浏览次数:25  
标签:要造 FastThreadLocal ThreadLocalMap index ThreadLocal 源码 线程 Netty

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

1.先从ThreadLocal说起

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

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

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 那样,使用哈希表来存储元素,而是使用了数组来进行元素存储,它的核心实现源码如下:

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

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

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 自动清除对象的实现核心源码如下:

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() 将集合中所有对象都清理掉,可以避免内存泄漏的问题,所以它的安全性更高。

课后思考

FastThreadLocal 有什么缺点?使用 FastThreadLocal 一定比 ThreadLocal 性能高吗?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

标签:要造,FastThreadLocal,ThreadLocalMap,index,ThreadLocal,源码,线程,Netty
From: https://www.cnblogs.com/vipstone/p/18217930

相关文章

  • netty建立数万客户端连接,并主动发消息
    @Slf4jpublicclassNettyClientTest{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupworkerEventLoopGroup=newNioEventLoopGroup();try{Bootstrapbootstrap=newBootstrap();boo......
  • 深入netty04-eventloop实现原理和最佳实践
    Reactor线程模型Reactor线程模型在网络框架设计中扮演着关键的角色。该模型通过事件分发器将读写事件分发给对应的事件处理者,以提高系统的吞吐量、可扩展性和安全性。常见的Reactor线程模型包括单线程模型、多线程模型和主从多线程模型。单线程模型:在单线程模型中,所有I......
  • 【图解IO与Netty系列】IO多路复用
    IO多路复用为什么要使用IO多路复用Linux的IO多路复用接口selectpollepoll为什么要使用IO多路复用我们常用的IO模型是BIO,我们Java里的IO流大多数都是BIO,也就是同步阻塞式IO,这种IO操作的好处是简单方便,但是缺点也很明显——性能不高。阻塞式IO的特点就是在数据未就绪......
  • Netty_Redis_Zookeeper高并发实战-读书笔记
    转载自:https://www.cnblogs.com/leihongzhi/p/17381156.html 第1章    高并发时代的必备技能1.nettyNetty是JBOSS提供的一个Java开源框架,基于NIO的客户端/服务器编程框架,能够快速开发高并发、高可用、高可靠的网络服务器程序,也能开发高可用、高可靠的客户端程序。NIO是......
  • 面试题剖析:Netty编解码如何解决拆包沾包问题?
    今天我们要聊的主题是Netty的编解码机制,特别是如何解决TCP的拆包和沾包问题。如果你曾在处理网络数据传输时遇到数据包混乱的情况,那么你已经体验过拆包和沾包的“乐趣”了。别担心,Netty提供了一系列强大的解码器,帮助我们轻松应对这些问题。本文将详细介绍这些解码器的工作原......
  • DotNetty ByteBuffer
    DotNetty是一个高性能的.NET网络通信框架,基于Netty,支持TCP、UDP、HTTP、WebSocket等协议。适用于高并发、低延迟场景,如实时通信、游戏服务器、IoT应用及大型分布式系统,通过异步I/O、零拷贝等技术提升性能,具备易用性、可扩展性。架构上,围绕Channel、EventLoop、ChannelPipel......
  • Netty ChannelHandler的生命周期
    ChannelHandler方法的执行是有顺序的,而这个执行顺序可以被称为ChannelHandler的生命周期。 LifeCyCleTestHandlerimportio.netty.channel.ChannelInboundHandlerAdapter;importio.netty.channel.ChannelHandlerContext;publicclassLifeCyCleTestHandlerextendsChan......
  • Netty 客户端与服务端收发消息demo
    客户端与服务端收发消息,要实现的具体功能是:在控制台输入一条消息之后按回车键,校验完客户端的登录状态之后,把消息发送到服务端;服务端收到消息之后打印,并向客户端发送一条消息,客户端收到消息之后打印。 客户端NettyClientimportcom.xc.xcspringboot.x_netty.client.handler.*;......
  • netty 最简demo
    Netty是什么Netty到底是何方神圣?用一句简单的话来说就是:Netty封装了JDK的NIO,让你用得更方便,不用再写一大堆复杂的代码了。用官方正式的话来说就是:Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务端和客户端。使用Netty而不使用JDK原生NIO的原因1.使用J......
  • java netty 实现 websocket 服务端和客户端双向通信 实现心跳和断线重连 完整示例
    javanetty实现websocket服务端和客户端双向通信实现心跳和断线重连完整示例maven依赖<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.97.Final</version></dependency>服务端一个接口IGet......