Netty源码实战(十) - 性能优化(上)

简介: Netty源码实战(十) - 性能优化(上)

1 性能优化工具类

1.1 FastThreadLocal

1.1.1 传统的ThreadLocal

ThreadLocal最常用的两个接口是set和get

最常见的应用场景为在线程上下文之间传递信息,使得用户不受复杂代码逻辑的影响

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
 t.threadLocals;

我们使用set的时候实际上是获取Thread对象的threadLocals属性,把当前ThreadLocal当做参数然后调用其set(ThreadLocal,Object)方法来设值

threadLocals是ThreadLocal.ThreadLocalMap类型的

1.png

每个线程对象关联着一个ThreadLocalMap实例,主要是维护着一个Entry数组

Entry是扩展了WeakReference,提供了一个存储value的地方

一个线程对象可以对应多个ThreadLocal实例,一个ThreadLocal也可以对应多个Thread对象,当一个Thread对象和每一个ThreadLocal发生关系的时候会生成一个Entry,并将需要存储的值存储在Entry的value内


  • 一个ThreadLocal对于一个Thread对象来说只能存储一个值,为Object型
  • 多个ThreadLocal对于一个Thread对象,这些ThreadLocal和线程相关的值存储在Thread对象关联的ThreadLocalMap中
  • 使用扩展WeakReference的Entry作为数据节点在一定程度上防止了内存泄露
  • 多个Thread线程对象和一个ThreadLocal发生关系的时候其实真实数据的存储是跟着线程对象走的,因此这种情况不讨论


我们在看看ThreadLocalMap#set:

Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
     e != null;
     e = tab[i = nextIndex(i, len)]) {
    ThreadLocal k = e.get();
    if (k == key) {
        e.value = value;
        return;
    }
    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

每个ThreadLocal实例都有一个唯一的threadLocalHashCode初始值

上面首先根据threadLocalHashCode值计算出i,有下面两种情况会进入for循环:


由于threadLocalHashCode &(len-1)对应的槽有内容,因此满足tab[i]!=null条件,进入for循环,如果满足条件且当前key不是当前threadlocal只能说明hash冲突了


ThreadLocal实例之前被设置过,因此满足tab[i]!=null条件,进入for循环


进入for循环会遍历tab数组,如果遇到以当前threadLocal为key的槽,即上面第(2)种情况,有则直接将值替换

如果找到了一个已经被回收的ThreadLocal对应的槽,也就是当key==null的时候表示之前的threadlocal已经被回收了,但是value值还存在,这也是ThreadLocal内存泄露的地方。碰到这种情况,则会引发替换这个位置的动作

如果上面两种情况都没发生,即上面的第(1)种情况,则新创建一个Entry对象放入槽中

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);
}

当命中的时候,也就是根据当前ThreadLocal计算出来的i恰好是当前ThreadLocal设置的值的时候,可以直接根据hashcode来计算出位置,当没有命中的时候,这里没有命中分为三种情况:

  • 当前ThreadLocal之前没有设值过,并且当前槽位没有值。
  • 当前槽位有值,但是对于的不是当前threadlocal,且那个ThreadLocal没有被回收。
  • 当前槽位有值,但是对于的不是当前threadlocal,且那个ThreadLocal被回收了。


上面三种情况都会调用getEntryAfterMiss方法。调用getEntryAfterMiss方法会引发数组的遍历。


总结一下ThreadLocal的性能,一个线程对应多个ThreadLocal实例的场景中

在没有命中的情况下基本上一次hash就可以找到位置

如果发生没有命中的情况,则会引发性能会急剧下降,当在读写操作频繁的场景,这点将成为性能诟病。

1.1.2 实例

1.png

两个线程操作同一object 对象,显然非线程安全,但是由于使用了 FTL, 线程安全!

image.png

结果表明内存地址不同,并非操作同一个 object!

image.png

让T1每1s 中新生成一个 object 对象

T2验证当前 object 是否与之前状态相同

image.png

显然,每个线程拿到的对象都是线程独享的!

某线程对变量的修改不影响其他线程!

通过对象隔离优化了程序性能!

1.1.3 Netty FastThreadLocal源码解析

image.png

1.1.3.1 创建

image.png

创建时重写一下初始值方法

image.png

实际上在构造FastThreadLocal实例的时候就决定了这个实例的索引

image.png

index 为 private 且非 static, 说明每个实例都有该值

再看看索引的生成相关代码

image.png

index 从0开始计数

image.png

image.png

nextIndex是InternalThreadLocalMap父类的一个全局静态的AtomicInteger类型的对象,这意味着所有的FastThreadLocal实例将共同依赖这个指针来生成唯一的索引,而且是线程安全的

Netty重新设计了更快的FastThreadLocal,主要实现涉及


  • FastThreadLocalThread
  • FastThreadLocal
  • InternalThreadLocalMap


FastThreadLocalThread是Thread类的简单扩展,主要是为了扩展threadLocalMap属性

image.png

FastThreadLocal提供的接口和传统的ThreadLocal一致,主要是set和get方法,用法也一致

不同地方在于FastThreadLocal的值是存储在InternalThreadLocalMap这个结构里面的,传统的ThreadLocal性能槽点主要是在读写的时候hash计算和当hash没有命中的时候发生的遍历,我们来看看FastThreadLocal的核心实现


InternalThreadLocalMap实例和Thread对象一一对应

UnpaddedInternalThreadLocalMap维护着一个数组:


image.pngimage.pngimage.png

image.png

这个数组用来存储跟同一个线程关联的多个FastThreadLocal的值,由于FastThreadLocal对应indexedVariables的索引是确定的,因此在读写的时候将会发生随机存取,非常快。


另外这里有一个问题,nextIndex是静态唯一的,而indexedVariables数组是实例对象的,因此我认为随着FastThreadLocal数量的递增,这会造成空间的浪费


1.1.3.2 get方法实现

获取 ThreadLocalMap

image.png

image.png

image.png

image.png

首先拿到当前线程,再判断是否为 FTL 线程快速获取否则慢速获取

让我们先分析一下 slowGet方法

image.png

首先会获取一个 ThreadLocal 变量

image.png

拿到 JDK 的 ThreadLocal 变量,用于给每个线程拿到InternalThreadLocalMap变量,所以过程较慢,该方法称为 slowGet 可想而知!

image.png

由于在创建 ThreadLocal 时,并没有重写 initValue 方法,所以可能为 null

接下啦看 fastGet 方法

image.png

image.png

image.png

直接通过索引取出对象

image.png

image.png

通过每个线程独享的 ThreadLocalMap 对象借助在 JVM 中每个 FTL 的唯一索引


目录
相关文章
|
2月前
Netty实战: HTTP文件列表服务器
Netty实战: HTTP文件列表服务器
24 0
|
3月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
60 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
1月前
|
网络协议 Java 测试技术
阿里内部Netty实战小册,值得拥有
Netty 是一个高性能的 Java 网络通信框架,简化了网络编程并涵盖了最新的Web技术。它提供了一种抽象,降低了底层复杂性,使得更多开发者能接触网络编程。Netty 因其易用性、高效性和广泛的应用场景受到推崇,适合互联网行业从业者学习,有助于理解和开发基于Netty的系统。免费的《Netty实战小册》详细介绍了Netty的各个方面,包括概念、架构、编解码器、网络协议和实际案例,帮助读者深入理解和应用Netty。如需完整版小册,可点击链接获取。
阿里内部Netty实战小册,值得拥有
|
2月前
|
NoSQL Redis
Netty实战:模拟Redis的客户端
Netty实战:模拟Redis的客户端
17 0
|
4月前
|
分布式计算 前端开发 网络协议
13W字!腾讯高工手写“Netty速成手册”,3天能走向实战
在java界,netty无疑是开发网络应用的拿手菜。你不需要太多关注复杂的nio模型和底层网络的细节,使用其丰富的接口,可以很容易的实现复杂的通讯功能。
|
4月前
|
监控 网络协议 调度
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
133 0
|
4月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
48 0
|
4月前
|
编解码 安全 前端开发
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
46 0
|
5月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
47 1
|
5月前
|
缓存 NoSQL Java
聚焦实战技能,剖析底层原理:Netty+Redis+ZooKeeper+高并发实战
移动时代、5G时代、物联网时代的大幕已经开启,它们对于高性能、高并发的开发知识和技术的要求,抬升了Java工程师的学习台阶和面试门槛。