文章目录
- 一、前言
- [二、多线程共享 FastThreadLocal](#二、多线程共享 FastThreadLocal)
-
- [1. FastThreadLocal 的使用](#1. FastThreadLocal 的使用)
- [2. FastThreadLocal 原理简述](#2. FastThreadLocal 原理简述)
-
- [2.1 InternalThreadLocalMap](#2.1 InternalThreadLocalMap)
-
- [2.1.1 InternalThreadLocalMap 的构造](#2.1.1 InternalThreadLocalMap 的构造)
- 2.1.1 InternalThreadLocalMap#get
- [2.2 FastThreadLocal 构造函数](#2.2 FastThreadLocal 构造函数)
- 2.3 FastThreadLocal#get
- 2.3 FastThreadLocal#set
- [3. FastThreadLocal 相较于 ThreadLocal 的优化](#3. FastThreadLocal 相较于 ThreadLocal 的优化)
- [4. InheritableThreadLocal 和 TransmittableThreadLocal](#4. InheritableThreadLocal 和 TransmittableThreadLocal)
- [三、Recycler 对象回收](#三、Recycler 对象回收)
- 四、参考内容
一、前言
本系列虽说本意是作为 《Netty4 核心原理》一书的读书笔记,但在实际阅读记录过程中加入了大量个人阅读的理解和内容,因此对书中内容存在大量删改。
本篇涉及内容 :第十四章 Netty高性能调优工具类解析
书中 第十三章 基于 Netty 手写消息推送系统 内容本系列不记录。
本系列内容基于 Netty 4.1.73.Final 版本,如下:
xml
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.73.Final</version>
</dependency>
系列文章目录:
二、多线程共享 FastThreadLocal
我们在 之前的文章中曾简单介绍过 FastThreadLocal,他类似于 JDK 的 ThreadLocal,也是用于多线程条件下,保证统一线程的对象共享,只是 Netty 中定义的 FastThreadLocal 性能要高于 JDK 的 ThreadLocal。
1. FastThreadLocal 的使用
FastThreadLocal 的使用与 ThreadLocal 完全相同,如下:
java
public static void main(String[] args) {
FastThreadLocal<String> threadLocal = new FastThreadLocal<>() {
@Override
protected String initialValue() {
return "init";
}
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> {
try {
log.info("start {}", threadLocal.get());
threadLocal.set(String.valueOf(Thread.currentThread().getId()));
log.info("end {}", threadLocal.get());
} finally {
threadLocal.remove();
}
});
ThreadUtil.sleep(1000);
}
2. FastThreadLocal 原理简述
在分析 FastThreadLocal 之前我们需要先了解 ThreadLocal 的基本原理,这部分内容在 ThreadLocal使用及源码分析 中有过分析(已经过去这么久了,岁月如梭啊 ),因此本文不再说明。
在介绍 FastThreadLocal 之间我们需要先介绍下 InternalThreadLocalMap,
2.1 InternalThreadLocalMap
InternalThreadLocalMap 是 Netty 内部用于优化线程本地变量存储的核心类,是 FastThreadLocal 机制的底层支撑。它通过数组而非哈希表存储线程本地变量,实现了更高效的访问性能,
InternalThreadLocalMap 替代了 JDK 原生的 ThreadLocalMap,为 FastThreadLocal 提供存储容器,主要解决以下问题:
- 避免 JDK ThreadLocal 中哈希表的哈希计算和冲突处理开销。
- 提供 O (1) 时间复杂度的变量访问(通过数组下标)。
- 支持批量清理线程本地变量,减少内存泄漏风险。
我们这里简单来看 InternalThreadLocalMap 下面几点:
2.1.1 InternalThreadLocalMap 的构造
InternalThreadLocalMap 的构造方法如下,
java
private InternalThreadLocalMap() {
// 初始化存储线程本地变量的数组,使用默认初始容量
indexedVariables = newIndexedVariableTable();
}
private static Object[] newIndexedVariableTable() {
// 创建指定初始容量的数组(INDEXED_VARIABLE_TABLE_INITIAL_SIZE 默认为32,容量不足时自动扩容)
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
// 将数组所有元素初始化为UNSET(一个特殊的标记对象)
// UNSET用于区分"未初始化"和"值为null"两种状态
Arrays.fill(array, UNSET);
return array;
}
通过上面的构造函数我们可以知道下面几点:
-
InternalThreadLocalMap 内部使用 Object[] 数组存储线程本地变量。
InternalThreadLocalMap 与 FastThreadLocal 是一对多,而 FastThreadLocal 与 Thread 是一对一,所以 InternalThreadLocalMap 与 Thread 是一对多,每个FastThreadLocal 在初始化的时候会的构造函数中会 InternalThreadLocalMap#nextVariableIndex 为自身分配一个唯一的 index,这个 index 代表当前 FastThreadLocal 的变量存储在 InternalThreadLocalMap#indexedVariables 数组的第几个元素中。
-
InternalThreadLocalMap 会为 Object[] 每个元素分配一个 UNSET 默认值,UNSET 值默认为未初始化,null 则表示初始化的值为 null。
2.1.1 InternalThreadLocalMap#get
InternalThreadLocalMap#get 用于从 ThreadLocal 中获取变量,具体实现如下:
java
public static InternalThreadLocalMap get() {
// 获取当前执行线程的实例
Thread thread = Thread.currentThread();
// 检查当前线程是否是Netty自定义的FastThreadLocalThread
if (thread instanceof FastThreadLocalThread) {
// 若是Netty自定义线程,走高效获取路径
return fastGet((FastThreadLocalThread) thread);
} else {
// 若是普通线程,走兼容获取路径(性能略低)
return slowGet();
}
}
/**
* 针对FastThreadLocalThread的高效获取方法
* 直接访问线程内部持有的InternalThreadLocalMap引用,避免中间层开销
*
* @param thread 类型为FastThreadLocalThread的当前线程
* @return 当前线程关联的InternalThreadLocalMap实例(非null)
*/
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
// 直接从线程实例中获取已关联的InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
// 如果线程尚未关联InternalThreadLocalMap,创建并绑定
if (threadLocalMap == null) {
// 1. 创建新的InternalThreadLocalMap实例
// 2. 通过setThreadLocalMap方法绑定到当前线程
// 3. 赋值给threadLocalMap变量(一行代码完成三步操作)
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
/**
* 针对普通线程的兼容获取方法
* 通过JDK原生ThreadLocal间接获取,性能略低于fastGet
*
* @return 当前线程关联的InternalThreadLocalMap实例(非null)
*/
private static InternalThreadLocalMap slowGet() {
// slowThreadLocalMap是一个JDK原生ThreadLocal实例,用于关联普通线程和InternalThreadLocalMap
// 这里通过它获取当前线程已关联的InternalThreadLocalMap
InternalThreadLocalMap ret = slowThreadLocalMap.get();
// 如果普通线程尚未关联InternalThreadLocalMap,创建并绑定
if (ret == null) {
// 创建新的InternalThreadLocalMap实例
ret = new InternalThreadLocalMap();
// 通过JDK ThreadLocal绑定到当前线程
slowThreadLocalMap.set(ret);
}
return ret;
}
这里我们可以知道:
- InternalThreadLocalMap#fastGet 方法针对于 FastThreadLocalThread 场景,这是 Netty 做了优化的处理,因此更快。 fastGet 方法更快的原因如下:
- 直接引用访问:FastThreadLocalThread 内部直接持有 InternalThreadLocalMap 成员变量,通过 threadLocalMap() 方法直接获取,无需任何哈希计算或间接查找,是真正的 O (1) 操作。
- 延迟初始化:仅在首次调用时创建 InternalThreadLocalMap 并绑定到线程,避免资源浪费。
- InternalThreadLocalMap#slowGet 则使用原生 JDK 的方式,相较于 Netty 的优化版本会更慢一些。
上面分析了 InternalThreadLocalMap 的部分内容,下面我们来看看 FastThreadLocal 的部分方法。
2.2 FastThreadLocal 构造函数
FastThreadLocal 构造函数如下,其中会调用 InternalThreadLocalMap.nextVariableIndex() 来获取自己的下标
java
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
其中 InternalThreadLocalMap#nextVariableIndex 的实现如下:
java
private static final AtomicInteger nextIndex = new AtomicInteger();
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 代表当前线程的变量存储在 InternalThreadLocalMap#indexedVariables 数组的第几个元素中。
如 FastThreadLocal 实例 A 的 index 为 3,则其绑定的变量值存储在 indexedVariables[3]
2.3 FastThreadLocal#get
FastThreadLocal#get 是 Netty 对 JDK 原生 ThreadLocal 的优化实现,用于快速获取当前线程关联的线程本地变量。FastThreadLocal#get 实现如下:
java
/**
* 获取当前线程绑定的线程本地变量值
* 如果变量未初始化,则通过initialize()方法完成初始化并绑定
*
* @return 当前线程关联的变量值,非null(除非initialValue()返回null)
*/
public final V get() {
// 1. 获取当前线程对应的InternalThreadLocalMap
// (Netty自定义的线程本地变量存储容器,替代JDK的ThreadLocalMap)
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// 2. 通过当前FastThreadLocal的唯一索引(index)直接访问数组中的值
// (相比JDK的哈希表查找,数组下标访问效率更高,O(1)时间复杂度)
Object v = threadLocalMap.indexedVariable(index);
// 3. 如果值已初始化(不等于UNSET标记),直接返回
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
// 4. 若未初始化,调用initialize()方法完成初始化并返回结果
return initialize(threadLocalMap);
}
下面我们按照上面注释来说明这几步:
-
获取存储容器:通过 InternalThreadLocalMap.get() 获取当前线程绑定的专属存储容器,该容器内部用数组存储线程本地变量。
-
直接索引访问:利用 index(每个 FastThreadLocal 实例的唯一标识)直接从数组中获取值,避免了 JDK ThreadLocal 的哈希计算和冲突处理,这是性能优化的关键。
-
状态判断:InternalThreadLocalMap.UNSET 是一个特殊标记,用于表示变量尚未初始化(区别于 null,因为用户可能需要存储 null 作为有效值)。
-
延迟初始化:若变量未初始化,则委托 initialize() 方法执行初始化逻辑(通常会调用 initialValue() 生成初始值,并将其绑定到当前线程的 InternalThreadLocalMap 中)。
2.3 FastThreadLocal#set
FastThreadLocal#set 用于为当前线程的 FastThreadLocal 实例设置值,是 FastThreadLocal 核心操作之一。其实现基于数组索引访问,相比 ThreadLocal 的哈希表操作,具有更高的效率。该方法具体实现如下:
java
public final void set(V value) {
// 1. 判断是否没有被设值
if (value != InternalThreadLocalMap.UNSET) {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
setKnownNotUnset(threadLocalMap, value);
} else {
remove();
}
}
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
// 直接通过索引设置数组值,无需哈希计算
if (threadLocalMap.setIndexedVariable(index, value)) {
// 若为首次设置,将当前FastThreadLocal添加到map的"已注册"集合中,便于后续清理
addToVariablesToRemove(threadLocalMap, this);
}
}
3. FastThreadLocal 相较于 ThreadLocal 的优化
-
数据结构:数组索引访问 vs 哈希表查找
-
ThreadLocal 的实现:JDK 的 ThreadLocal 依赖线程(Thread)中的 ThreadLocalMap 存储数据。ThreadLocalMap 是一种自定义的哈希表,其 key 是 ThreadLocal 实例本身,value 是线程本地变量的值。
当调用 get() 方法时,需要通过哈希计算确定 key 在哈希表中的位置,若发生哈希冲突,还需通过「线性探测」法查找下一个可用位置。这种哈希表的查找、扩容等操作存在一定的性能开销。
-
FastThreadLocal 的实现:FastThreadLocal 基于 Netty 自定义的 InternalThreadLocalMap,其底层使用数组存储数据。每个 FastThreadLocal 实例会被分配一个唯一的整数索引(通过静态计数器生成),变量的存储和读取直接通过该索引操作数组(array[index])。
这种「索引直接访问」方式完全避免了哈希计算和哈希冲突的处理,访问效率接近数组的 O (1) 操作,远快于哈希表的查找。
-
-
线程绑定:更直接的内存访问
- ThreadLocal:要获取线程的 ThreadLocalMap,需要通过 Thread.currentThread().threadLocals 间接访问,这涉及到对象属性的多级访问(线程实例 → ThreadLocalMap 实例)。
- FastThreadLocal:要求线程必须是 Netty 自定义的 FastThread(或其子类),这类线程直接持有 InternalThreadLocalMap 的引用,且通过 Unsafe 类(底层直接操作内存)快速获取该 map,减少了中间环节的开销。
-
内存布局:连续内存的缓存友好性
数组的内存是连续的,而哈希表(如 ThreadLocalMap)的节点内存通常是分散的。CPU 缓存对连续内存的访问效率更高(缓存命中率高),因此 FastThreadLocal 基于数组的实现更利于 CPU 缓存利用,进一步提升访问速度。
-
清理机制:更高效的资源释放
- ThreadLocal 的清理(如 remove())需要遍历哈希表,找到对应 key 才能删除,操作相对繁琐。
- FastThreadLocal 由于通过索引定位,清理时只需直接操作数组对应位置,且 InternalThreadLocalMap 提供了批量清理机制,效率更高。
FastThreadLocal 给出的灵感:并不需要一味追求 Map 结构,在一些固定场景下,直接使用数组结构效率可能更高。
4. InheritableThreadLocal 和 TransmittableThreadLocal
ThreadLocal为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,但 ThreadLocal 无法跨线程传递变量,因此 JDK 提供了 InheritableThreadLocal
但因为 InheritableThreadLocal 无法解决线程复用导致的上下文丢失问题,所以在线程池场景下 可使用阿里开源的TransmittableThreadLocal。
三、Recycler 对象回收
Recycler 是 Netty 实现的一个轻量级对象回收站,很多对象使用完毕之后并没有直接交给 GC 处理,而是通过对象回收站讲对象回收,目的是为了对象重用和减少 GC 压力。
比如 ByteBuf 对象的回收,因为 ByteBuf 对象在 Netty 中会被频繁创建,并且占用比较大的内存空间,所以使用完毕后会通过对象回收站的方式进行回收,以达到资源重用的目的。
在 Netty 中 Recycler 的使用相当频繁,以下面 Demo为例:
java
public class RecyclerDemo {
public static void main(String[] args) {
Recycler<MyObject> recycler = new Recycler<>() {
protected MyObject newObject(Handle<MyObject> handle) {
return new MyObject(handle);
}
};
// 创建 myObject1 并释放
MyObject myObject1 = recycler.get();
myObject1.recycle();
// 创建 myObject2 并释放
MyObject myObject2 = recycler.get();
myObject2.recycle();
// 比较 myObject1 和 myObject2 是否是同一个对象 : true
System.out.println(myObject1 == myObject2);
}
public static class MyObject {
private final Recycler.Handle<MyObject> handle;
public MyObject(Recycler.Handle<MyObject> handle) {
this.handle = handle;
}
public void recycle() {
handle.recycle(this);
}
}
}
上面的代码会创建 myObject1 和 myObject2 两个对象,由于创建 myObject2 时 myObject1 已经回收,所以会被 myObject2 复用,因此这里在比较 myObject1 == myObject2 时实际为 true。
书中有具体的分析逻辑,但是由于不想写了,所以此处省略。
四、参考内容
- 豆包
- 《Netty4 核心原理》
- InheritableThreadLocal 详解