vault backup: 2026-11-07 16:33:44
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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() 将集合中所有对象都清理掉,可以避免内存泄漏的问题,所以它的安全性更高。
|
||||
|
||||
### 课后思考
|
||||
|
||||
FastThreadLocal 有什么缺点?使用 FastThreadLocal 一定比 ThreadLocal 性能高吗?
|
||||
Reference in New Issue
Block a user