本文是该篇的修正版
本文的github地址:点此

Netty 的 FastThreadLocal 源码解析

该文中涉及到的 Netty 源码版本为 4.1.6。

Netty 的 FastThreadLocal 是什么

A special variant of ThreadLocal that yields higher access performance when accessed from a FastThreadLocalThread.
Internally, a FastThreadLocal uses a constant index in an array, instead of using hash code and hash table, to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash table, and it is useful when accessed frequently.
To take advantage of this thread-local variable, your thread must be a FastThreadLocalThread or its subtype. By default, all threads created by DefaultThreadFactory are FastThreadLocalThread due to this reason.
Note that the fast path is only possible on threads that extend FastThreadLocalThread, because it requires a special field to store the necessary state. An access by any other kind of thread falls back to a regular ThreadLocal.

以上是 Netty 官方文档中关于 FastThreadLocal 的介绍。

简而言之,FastThreadLocal 是在 ThreadLocal 实现上的一种变种,相比 ThreadLocal 内部通过将自身 hash 的方式在 hashTable 上定位需要的变量存储位置,FastThreadLocal 选择在数组上的一个固定的常量位置来存放线程本地变量,这样的操作看起来并没有太大区别,但是相比 ThreadLocal 的确体现了性能上的优势,尤其是在读操作频繁的场景下。

如何使用 FastThreadLocal

如果想要得到 FastThreadLocal 的速度优势,必须通过 FastThreadLocalThread 或者其子类的线程,才可以使用,因为这个原因,Netty 的 DefaultThreadFactory,其内部默认线程工厂的 newThread()方法就是直接初始化一个 FastThreadLocalThread ,以便期望在 ThreadLocal 的操作中,得到其性能上带来的优势。

protected Thread newThread(Runnable r, String name) {return new FastThreadLocalThread(threadGroup, r, name);
}

FastThreadLocal 的源码实现

FastThreadLocal 被访问的入口

当需要用到 FastThreadLocal 的时候,想必和 jdk 原生的 ThreadLocal 的 api 类似,都是通过初始化一个新的 FastThreadLocal 之后,通过其 set()方法初始化并放入一个变量作为线程本地变量存储。

public final void set(V value) {if (value != InternalThreadLocalMap.UNSET) {set(InternalThreadLocalMap.get(), value);} else {remove();}
}

因此,在 FastThreadLocal 的 set()方法中,可以看到,存储本地线程变量的数据结构是一个 InternalThreadLocalMap。

private InternalThreadLocalMap threadLocalMap;

在 FastThreadLocalThread 中,因为本身 threadLocalMap 就是其中的一个成员,能够快速得到返回。而其他线程实现,就将面临没有这个成员的尴尬,Netty 也给出了相应的兼容。

public static InternalThreadLocalMap get() {Thread thread = Thread.currentThread();if (thread instanceof FastThreadLocalThread) {return fastGet((FastThreadLocalThread) thread);} else {return slowGet();}
}

InternalThreadLocalMap 的 get()方法中,当前线程如果是 FastThreadLocalThread 或是其子类的实现,变直接返回其 InternalThreadLocalMap 进行操作,但对于不属于上述条件的线程,Netty 通过 slowGet()的方式,也将返回一个 InternalThreadLocalMap。

private static InternalThreadLocalMap slowGet() {ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;InternalThreadLocalMap ret = slowThreadLocalMap.get();if (ret == null) {ret = new InternalThreadLocalMap();slowThreadLocalMap.set(ret);}return ret;
}

在 slowGet()方法中,当前线程对应的 InternalThreadLocalMap 会通过原生 jdk 下 ThreadLocal 的方式存储并通过 ThreadLocal 返回,因此,在这个场景下,使用的还是 jdk 原生的 ThreadLocal,但是只占用了原生 ThreadLocal 下的 Entry[]数组的一个位置,具体的变量还是存放在专门为 FastThreadLocal 服务的 InternalThreadLocalMap 中。
在此,随着 InternalThreadLocalMap 的得到并返回,针对 FastThreadLocal 的 get 和 set 操作,也将变为操作 InternalThreadLocalMap 来达到目的,FastThreadLocal 性能优越的原因,也在 InternalThreadLocalMap 当中。

InternalThreadLocalMap 的内部构造

static final AtomicInteger nextIndex = new AtomicInteger();Object[] indexedVariables;

InternalThreadlocalMap 主要由以上两个成员组成,其中 indexedVariables 作为一个 Object[]数组,直接用来存放 FastThreadLocal 对应的 value,每个 FastThreadLocal 对象都会在相应的线程的 ThreadLocalMap 中被分配到对应的 index,而这里的具体下标,则由以上的 nextIndex 成员在每个 FastThreadLocal 初始化的时候分配。

private final int index;public FastThreadLocal() {index = InternalThreadLocalMap.nextVariableIndex();
}

每个 FastThreadLocal 在构造方法的过程中,都会通过 InternalThreadlocalMap 的 nextVariableIndex()返回 nextIndex 自加后的结果作为其在 InternalThreadlocalMap 上的下标。后续该 FastThreadLocal 在操作变量的时候可以直接通过该 index 定位到 Object[]数组上的位置。

private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

而数组上的下标有一个特殊位,一般在其首位也就是 0 的位置,这个位置在 FastThreadLocal 类被加载的时候作为静态变量被设置。在这个位置上,存放的是一个 FastThreadLocal 对象集合,每个存放到 InternalThreadlocalMap 中的 FastThreadLocal 都会被保存在首位的集合中。

public static final Object UNSET = new Object();

另外,为了具体区分保存的变量是 null 还是不存在当前变量,InternalThreadLocalMap 中定义了一个为 NULL 的成员变量,以便区分上述情况,在一开始,InternalThreadLocalMap 中的 indexedVariables 数组都是 NULL。

FastThreadLocal 的 set()方法的源码分析

相比 FastThreadLocal 的 set 操作,get 方法的过程与逻辑都要简单的多,因此此处主要以其 set 方法为主。

public final void set(V value) {if (value != InternalThreadLocalMap.UNSET) {set(InternalThreadLocalMap.get(), value);} else {remove();}
}public final void set(InternalThreadLocalMap threadLocalMap, V value) {if (value != InternalThreadLocalMap.UNSET) {if (threadLocalMap.setIndexedVariable(index, value)) {addToVariablesToRemove(threadLocalMap, this);}} else {remove(threadLocalMap);}
}

在其 set()方法中,首先会判断 set 的值是否是 InternalThreadLocalMap 中的 NULL 对象来判断是 set 操作还是 remove 操作,如果不是,会通过 InternalThreadLocalMap.get()方法获取当前线程对应的 InternalThreadLocalMap,获取的过程在前文已经描述过。
之后的主要流程主要分为两步:

  • 调用 InternalThreadLocalMap 的 setIndexedVariable()方法,将该 FastThreadLocal 成员在构造方法中获得到的 InternalThreadLocalMap 上的下标作为入参传入。
public boolean setIndexedVariable(int index, Object value) {Object[] lookup = indexedVariables;if (index < lookup.length) {Object oldValue = lookup[index];lookup[index] = value;return oldValue == UNSET;} else {expandIndexedVariableTableAndSet(index, value);return true;}
}

在 InternalThreadLocalMap 的 setIndexedVariable()方法过程中,set 的过程并不复杂,找到对应的下标,并将对应的值放到 InternalThreadLocalMap 数组下标对应的位置上即宣告结束。但是,因为 FastThreadLocal 在构造过程中虽然预先获得了对应的下标,但是实际上的数组大小可能完全还没有达到相应的大小,就要在此处通过 expandIndexedVariableTableAndSet()方法进行扩容,由于是数组的缘故,只需要扩容后将原来的值复制过去,并将剩余的值用 NULL 对象填满即可。

  • 如果上一步 set 成功,通过 addToVariablesToRemove()方法将该 FastThreadLocal 对象放入到 InternalThreadLocalMap 的数组中的首位集合中。在这个集合中,对于 FastThreadLocal 是一个强引用。

这样,对于 FastThreadLocal 的一次 set 操作即宣告结束。

相比 ThreadLocal,FastThreadLocal 到底快在哪里

  • FastThreadLocal 在具体的定位的过程中,只需要根据在构造方法里获取得到的具体下标就可以定位到具体的数组位置进行变量的存取,而在 jdk 原生的 ThreadLocal 中,具体位置的下标获取不仅需要计算 ThreadLocal 的 hash 值,并需要在 hashTable 上根据 key 定位的结果,一旦定位之后的结果上已经存在其他 ThreadLocal 的变量,那么则是通过线性探测法,在 hashTable 上寻找下一个位置进行,相比 FastThreadLocal 定位的过程要复杂的多。
  • FastThreadLocal 由于采取数组的方式,当面对扩容的时候,只需要将原数组中的内容复制过去,并用 NULL 对象填满剩余位置即可,而在 ThreadLocal 中,由于 hashTable 的缘故,在扩容后还需要进行一轮 rehash,在这过程中,仍旧存在 hash 冲突的可能。
  • 在 FastThreadLocal 中,遍历当前线程的所有本地变量,只需要将数组首位的集合即可,不需要遍历数组上的每一个位置。
  • 在原生的 ThreadLocal 中,由于可能存在 ThreadLocal 被回收,但是当前线程仍旧存活的情况导致 ThreadLocal 对应的本地变量内存泄漏的问题,因此在 ThreadLocal 的每次操作后,都会进行启发式的内存泄漏检测,防止这样的问题产生,但也在每次操作后花费了额外的开销。而在 FastThreadLocal 的场景下,由于数组首位的 FastThreadLocal 集合中保持着所有 FastThreadLocal 对象的引用,因此当外部的 FastThreadLocal 的引用被置为 null,该 FastThreadLocal 对象仍旧保持着这个集合的引用,不会被回收掉,只需要在线程当前业务操作后,手动调用 FastThreadLocal 的 removeAll()方法,将会遍历数组首位集合,回收掉所有 FastThreadLocal 的变量,避免内存泄漏的产生,也减少了原生 ThreadLocal 的启发式检测开销。
private static final class DefaultRunnableDecorator implements Runnable {private final Runnable r;DefaultRunnableDecorator(Runnable r) {this.r = r;}@Overridepublic void run() {try {r.run();} finally {FastThreadLocal.removeAll();}}
}

在 Netty 的 DefaultThreadFactory 中,每个线程在执行为任务后都会调用 FastThreadLocal 的 removeAll()方法。

Netty技术细节源码分析-FastThreadLocal源码分析相关推荐

  1. netty中的future和promise源码分析(二)

    前面一篇netty中的future和promise源码分析(一)中对future进行了重点分析,接下来讲一讲promise. promise是可写的future,从future的分析中可以发现在其中没 ...

  2. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用 1...

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionPro ...

  3. 手机自动化测试:Appium源码分析之跟踪代码分析四 1

    手机自动化测试:Appium源码分析之跟踪代码分析四 控制器模块 // Appium webserver controller methods // https://github.com/hugs/a ...

  4. 【原创】【专栏】《Linux设备驱动程序》--- LDD3源码目录结构和源码分析经典链接

    http://blog.csdn.net/geng823/article/details/37567557 [原创][专栏]<Linux设备驱动程序>--- LDD3源码目录结构和源码分析 ...

  5. 【Linux 内核】进程管理 ( 进程相关系统调用源码分析 | fork() 源码 | vfork() 源码 | clone() 源码 | _do_fork() 源码 | do_fork() 源码 )

    文章目录 一.fork 系统调用源码 二.vfork 系统调用源码 三.clone 系统调用源码 四._do_fork 函数源码 五.do_fork 函数源码 Linux 进程相关 " 系统 ...

  6. 【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 )

    文章目录 一.分析 PathClassLoader 源码 二.分析 BaseDexClassLoader 源码 三.分析 PathDexList 源码 四. 源码资源 一.分析 PathClassLo ...

  7. 【Android 电量优化】JobScheduler 源码分析 ( JobServiceContext 源码分析 | 闭环操作总结 | 用户提交任务 | 广播接收者接受相关广播触发任务执行 )★

    文章目录 一.JobServiceContext 引入 二.JobServiceContext 源码分析 三.用户在应用层如何使用 JobScheduler 四.用户提交任务 五.广播接收者监听广播触 ...

  8. 老李推荐:第6章1节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览 1...

    老李推荐:第6章1节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览 在上一章中我们有简要的介绍了事件源是怎么一回事,但是并没有进行详细的描述.那么往下的这几个 ...

  9. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

最新文章

  1. 重庆python培训-重庆python培训机构排.行榜
  2. Docker Hub 官方仓库发布编程语言包
  3. (转)一步一步Asp.Net MVC系列_权限管理设计起始篇
  4. 某马Java架构师实战学习手册
  5. 从动力学角度看优化算法:一个更整体的视角
  6. 【POJ - 3125 】Printer Queue(模拟,队列+优先队列,STL)
  7. Tomcat本地正常!但是部署到服务器后,mysql插入中文乱码问题解决!
  8. C#LeetCode刷题-位运算
  9. android 遮罩 进度条,bootstrap创建带遮罩层的进度条
  10. HealthKit教程 Swift版:锻炼信息
  11. Java多线程:同步集合与同步锁
  12. tpc-ds99 工具使用
  13. python获取windows当前登录用户名 域 计算机名 mac地址 ip
  14. 视频画面大小剪裁操作教程
  15. matlab 单相整流电路,MATLAB的单相桥式整流电路研究
  16. 【Memcached】分布式内存对象缓存系统
  17. 网页百度网盘上传显示服务器错误,win7系统下登陆百度浏览器提示连接服务器错误的方案?...
  18. msp430流水灯c语言程序,基于MSP-EXP430F5529开发板流水灯程序
  19. php 没有读取权限能不能复制,“需要提供管理员权限才能复制此文件夹”怎么办...
  20. 千年疑惑:为什么我越累越失眠,越睡越疲惫?

热门文章

  1. 二叉链表之寻找两节点的最近公共祖先☆
  2. ASP.NET Core 2.1基于SignalR和Vue实时通讯
  3. 计算机电子的危害,电子垃圾焚烧炉的危害有多大,你了解过吗?
  4. Linux块层技术全面剖析-v0.1
  5. SIM800C Couldn't pair with xxx because of an incorrect PIN or passkey
  6. php 输出 jsonp
  7. mariadb数据库服务
  8. 【Python之旅】第二篇(七):集合
  9. MySQL到MsSQL的迁移工具——SSMA
  10. 解决无法使用locate命令的方法