池化内存分配三
FastThreadLocal
在前面几篇源码解析的课程中,我们都有在源码中发现 FastThreadLocal 的身影。顾名思义,Netty 作为高性能的网络通信框架,FastThreadLocal 是比 JDK 自身的 ThreadLocal 性能更高的通信框架。FastThreadLocal 到底比 ThreadLocal 快在哪里呢?
JDK ThreadLocal 基本原理
首先我们先学习下 Java 原生的 ThreadLocal 的实现原理,可以帮助我们更好地对比和理解 Netty 的 FastThreadLocal。
如果你需要变量在多线程之间隔离,或者在同线程内的类和方法中共享,那么 ThreadLocal 大显身手的时候就到了。ThreadLocal 可以理解为线程本地变量,它是 Java 并发编程中非常重要的一个类。ThreadLocal 为变量在每个线程中都创建了一个副本,该副本只能被当前线程访问,多线程之间是隔离的,变量不能在多线程之间共享。这样每个线程修改变量副本时,不会对其他线程产生影响。
ThreadLocal 无法解决共享对象的更新问题。因此建议使用 static 修饰,这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。(引用阿里巴巴编程规范)。
InheritableThreadLocal 是 TheadLocal 的一个子类,提供一种线程之间的属性继承关系。比如线程 A 创建线程 B,线程 B 可以拥有访问线程 A 创建的所有的 ThreadLocal 变量。
ThreadLocal 案例:
public class ThreadLocalTest { private static final ThreadLocal<String> THREAD_NAME_LOCAL = ThreadLocal.withInitial(() -> Thread.currentThread().getName()); private static final ThreadLocal<TradeOrder> TRADE_THREAD_LOCAL = new ThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 2; i++) { int tradeId = i; new Thread(() -> { TradeOrder tradeOrder = new TradeOrder(tradeId, tradeId % 2 == 0 ? "已支付" : "未支付"); TRADE_THREAD_LOCAL.set(tradeOrder); System.out.println("threadName: " + THREAD_NAME_LOCAL.get()); System.out.println("tradeOrder info:" + TRADE_THREAD_LOCAL.get()); }, "thread-" + i).start(); } } static class TradeOrder { long id; String status; public TradeOrder(int id, String status) { this.id = id; this.status = status; } @Override public String toString() { return "id=" + id + ", status=" + status; } } }
在上述示例中,构造了 THREAD_NAME_LOCAL 和 TRADE_THREAD_LOCAL 两个 ThreadLocal 变量,分别用于记录当前线程名称和订单交易信息。ThreadLocal 是可以支持泛型的,THREAD_NAME_LOCAL 和 TRADE_THREAD_LOCAL 存放 String 类型和 TradeOrder 对象类型的数据,你可以通过 set()/get() 方法设置和读取 ThreadLocal 实例。
运行结果:
threadName: thread-0 threadName: thread-1 tradeOrder info:id=1, status=未支付 tradeOrder info:id=0, status=已支付
可以看出 thread-1 和 thread-2 虽然操作的是同一个 ThreadLocal 对象,但是它们取到了不同的线程名称和订单交易信息。那么一个线程内如何存在多个 ThreadLocal 对象,每个 ThreadLocal 对象是如何存储和检索的呢?
查看源码可知,Thread内部都有一个ThreadLocalMap属性,它是 ThreadLocal 的内部类,ThreadLocalMap 其实与 HashMap 的数据结构类似,但是 ThreadLocalMap 不具备通用性,它是为 ThreadLocal 量身定制的。
ThreadLocalMap 是一种使用线性探测法实现的哈希表,底层采用数组存储数据。ThreadLocalMap 会初始化一个长度为 16 的 Entry 数组,每个 Entry 对象用于保存 key-value 键值对。与 HashMap 不同的是,Entry 的 key 就是 ThreadLocal 对象本身,value 就是用户具体需要存储的值。Entry 继承自弱引用类 WeakReference,Entry 的 key 是弱引用,value 是强引用。在 JVM 垃圾回收时,只要发现了弱引用的对象,不管内存是否充足,都会被回收。那么为什么 Entry 的 key 要设计成弱引用呢?我们试想下,如果 key 都是强引用,当 ThreadLocal 不再使用时,然而 ThreadLocalMap 中还是存在对 ThreadLocal 的强引用,那么 GC 是无法回收的,从而造成内存泄漏。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
线性探测法
用大小为 M 的数组保存 N 个键值对,其中 M>N。我们需要依靠数组中的空位解决Hash碰撞冲突。基于这种策略的所有方法被统称为开放地址散列表。开放地址散列表中最简单的方法叫做线性探测法: 当碰撞发生时(当一个键的散列值已经被另一个不同的键占用),我们直接检测散列表的下一个位置(将索引加一)。这样的线性探测可能会产生三种情况:
- 命中,该位置的键和被查找的键相同;
- 未命中,键为空(该位置没有键);
- 继续查找,该位置的键和被查找的键不同。
我们用散列函数找到键在数组中的索引,检查其中的键和被查找的键是否相同。如果不同则继续查找(将索引增大,达到数组结尾时折回数组的开头),直到找到该键或者遇到一个空元素。开放地址散列表的核心思想是与其将内存用作链表,不如将它们作为在散列表的空元素。这此空元素可以作为查找结束的标志。
线性探测算法示意图
开放地址类的散列表的性能依赖公式 α = N/M,表示已被占用空间的比例,一般低于 1/8 到1/2 之间会有较好的性能。
还有一个值得注意的是删除操作,我们不能直接将该键的位置置为 NULL,否则位于此位置之后的元素无法被查找。因此,我们需要将被删除键的右侧所有的键重新插入散列表中。
ThreadLocalMap 如何实现开放地址列表
每个 ThreadLocal 在初始化时都会有一个 threadLocalHashCode 值,而这个值是通过静态方法 nextHashCode() 得到的,这个方法很有趣,每增加一个 ThreadLocal 对象,Hash 值就会固定增加一个魔术值 HASH_INCREMENT(0x61c88647)。实验证明,通过累加魔术值 0x61c88647 生成的 threadLocalHashCode 与 2 的次幂取模,得到的结果可以较为均匀地分布在 2 的次幂大小的数组中(据说与斐波那契散列法以及黄金分割有关)。相关源码解析如下,图文配合理解使用更佳。
下面这图是详解插入 K 的时候是如何进行的。
ThreadLocal 内存泄漏
弱引用 是指当一个对象仅仅被弱引用指向,而没有任何强引用指向时,如果这里GC运行,那么这个对象就会被回收,不论当前内存空间是否足够。
ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有任何强引用指向时,那么这个 ThreadLocal 势必会被 GC 回收。因此,就会出现 key == null 的 Entry(注意 key == null 和 tab[i] == null 在这里是不同的概念),也就没有办法访问这些 key 为 null 的 Entry 的 value。如果当前线程还未结束,这些 key==null 的 Entry 就会一直存在强引用链而得不到释放,只能等待线程销毁,从而无法回收造成内存泄漏。
如何避免 ThreadLocal 内存泄漏
虽然 TheadLocal 在执行 ThreadLocal#set() 或 ThreadLocal#get() 方法时会清除 ThreadLocalMap 中 key==NULL 的 Entry 对象,从而避免内存泄漏。但最佳实践还是应该每次使用完 ThreadLocal,调用它的 remove() 方法清除数据,而不是 set(null),这可能会导致内存泄漏。如果是在异常的场景中,记得在 finally 代码块中进行清理,保持良好的编码意识。
FastThreadLocal
demo
public class FastThreadLocalTest { private static final FastThreadLocal<String> THREAD_NAME_LOCAL = new FastThreadLocal<>(); private static final FastThreadLocal<TradeOrder> TRADE_THREAD_LOCAL = new FastThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 2; i++) { int tradeId = i; String threadName = "thread-" + i; new FastThreadLocalThread(() -> { THREAD_NAME_LOCAL.set(threadName); TradeOrder tradeOrder = new TradeOrder(tradeId, tradeId % 2 == 0 ? "已支付" : "未支付"); TRADE_THREAD_LOCAL.set(tradeOrder); System.out.println("threadName: " + THREAD_NAME_LOCAL.get()); System.out.println("tradeOrder info:" + TRADE_THREAD_LOCAL.get()); }, threadName).start(); } } }
由于 ThreadLocal 是在空间和时间之间寻求平衡,较好兼顾时间和性能。但是,Netty 通过理解 ThreadLocal 使用场景,觉得时间至上,最后利用空间换时间的思想重新设置了新的 FastThreadLocal,并配套实现了 FastThreadLocalThread 和 InternalThreadLocalMap 两个重要的类。FastThreadLocal、FastThreadLocalThread、InternalThreadLocalMap 是和 ThreadLocal、Thread、ThreadLocalMap 是对等的,我们可以按照 ThreadLocal 的逻辑理解它们,区别是底层的实现不同。FastThreadLocal 处理方式是直接就给每个线程设置一个InternalThreadLocalMap,不管是FastThreadLocalThread直接设置,还是JDK的普通线程Thread,设置进ThreadLocalMap间接设置。InternalThreadLocalMap内部维护着一个对象数组和索引,要放进去直接将值放进对象数组,返回一个索引,记录在FastThreadLocal中,取的时候直接拿这个索引去对象数组里取,非常方便。但是这样的一个缺点就是内存消耗比较大,因为只会扩容,而且索引只会递增,这样数组就会越来越大。所以就是空间换时间了。
InternalThreadLocalMap
TheadLocal,使用 ThreadLocalMap 存储数据,而 ThreadLocalMap 底层采用线性探测法解决 Hash 冲突问题,在空间和时间上寻求平衡。但 Netty 对这样的平衡并不满意,因此重新设计,使用 InternalThreadLocalMap 存储数据。核心思想是以空间换时间。可以把 InternalThreadLocalMap 当成一个数据结构,这里主要用到 nextIndex 和 Object[] 两个变量存储相应数据。利用 nextIndex 确认位置:每创建一个 FastThreadLocal 对象就从 InternalTheadLocalMap#getAndIncrement() (即 nextIndex 对象)方法获取索引值并保存在 FastThreadLocal#index 变量中,这个索引对应 Object[] 下标对应,通过索引值就能获取 FastThreadLocal 对象保存的值,这对于频繁获取值是非常高效的。其中有一个特殊情况, Object[0] 会存储一个 Set 集合,记录已创建的 FastThreadLocal 对象,方便在 removeAll() 方法中只需要遍历 Object[0] 的 Set 集合找到对应的数据并删除。Object[] 数组结构示意图:
这个定义了一些跟线程独有的属性,slowThreadLocalMap 其实就是用了原始的ThreadLocal,但是存的是InternalThreadLocalMap,就是普通线程用FastThreadLocal的时候先创建一个InternalThreadLocalMap放入,然后后面就可以取来用了,这个过程当然比FastThreadLocalThread直接获取InternalThreadLocalMap慢啦。
private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>(); private static final AtomicInteger nextIndex = new AtomicInteger();//索引 public static final Object UNSET = new Object(); /** Used by {@link FastThreadLocal} */ private Object[] indexedVariables;//放对象的数组 // Core thread-locals private int futureListenerStackDepth;//未来监听器栈的深度 private int localChannelReaderStackDepth;//本地通道读的栈深度 private Map<Class<?>, Boolean> handlerSharableCache;//处理器共享缓存 private IntegerHolder counterHashCode; private ThreadLocalRandom random; private Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache;//参数类型匹配缓存 private Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache;//参数类型匹配寻找缓存 // String-related thread-locals private StringBuilder stringBuilder; private Map<Charset, CharsetEncoder> charsetEncoderCache;//编码器缓存 private Map<Charset, CharsetDecoder> charsetDecoderCache;//解码器缓存 // ArrayList-related thread-locals private ArrayList<Object> arrayList;
构造函数
直接创建32
个空对象的数组。
private InternalThreadLocalMap() { indexedVariables = newIndexedVariableTable(); }
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
Arrays.fill(array, UNSET);
return array;
}
getIfSet
获取InternalThreadLocalMap
,如果是FastThreadLocalThread
就直接获取InternalThreadLocalMap
,如果不是就用ThreadLocal
获取,获取不到就返回null
。
public static InternalThreadLocalMap getIfSet() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return ((FastThreadLocalThread) thread).threadLocalMap();//快速获取 } return slowThreadLocalMap.get();//常规获取 }
get
获取InternalThreadLocalMap
,如果获取不到就创建一个,如果是FastThreadLocalThread
就用快速的方法,否则就慢的方法。
public static InternalThreadLocalMap get() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return fastGet((FastThreadLocalThread) thread); } else { return slowGet();//如果非 FastThreadLocalThread,通过ThreadLocal对象保存一个 InternalThreadLocalMap 对象,间接实现。 } }
fastGet
快方法就是直接获取,获取不到就创建一个设置进去,返回。
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); if (threadLocalMap == null) { thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap()); } return threadLocalMap; }
slowGet
非FastThreadLocalThread线程类型获取比较曲折。首先,Thread内存没有InternalThreadLocalMap变量,因此需要通过ThreadLocal变相保存InternalThreadLocalMap。因此慢获取一个值会经历两个步骤: ① 首先通过slowGet()方法获取InternalThreadLocalMap对象, ② 从InternalThreadLocalMap对象获取值,所以还不如直接使用ThreadLocal保存对象值来得更快些。因此,如果FastThreadLocal没有配合FastThread使用的话,可能性能会变得更慢
private static InternalThreadLocalMap slowGet() { ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;//获取一个ThreadLocal<InternalThreadLocalMap>()对象 InternalThreadLocalMap ret = slowThreadLocalMap.get();//从「ThreadLocal」中获取InternalThreadLocalMap对象,里面保存用户数据 if (ret == null) { ret = new InternalThreadLocalMap();//// 没有则初始化InternalThreadLocalMap slowThreadLocalMap.set(ret);//// 添加到ThreadLocal对象中 } return ret; }
nextVariableIndex
获取索引值,FastThreadLocal
构造的时候需要,因为有索引值才可以从数组中获取值啊。
public static int nextVariableIndex() { int index = nextIndex.getAndIncrement();//获取后自增 if (index < 0) {//溢出就抛异常了 nextIndex.decrementAndGet(); throw new IllegalStateException("too many thread-local indexed variables"); } return index; }
setIndexedVariable
设置索引和对应的值,如果在范围内,就替换旧值,返回旧值是否是UNSET
,是就表示第一次设置,返回true
,不是就表示更新,返回false
。超出范围就扩容。
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; } }
expandIndexedVariableTableAndSet
扩容到大于index
的最小的2
的幂次,比如index=32
,扩容到64
,然后把老的数组拷贝到新的数组里去,不满的地方用UNSET
填满。扩容源码和JDK HashMap扩容一样。
private void expandIndexedVariableTableAndSet(int index, Object value) { Object[] oldArray = indexedVariables; final int oldCapacity = oldArray.length; int newCapacity = index; newCapacity |= newCapacity >>> 1; newCapacity |= newCapacity >>> 2; newCapacity |= newCapacity >>> 4; newCapacity |= newCapacity >>> 8; newCapacity |= newCapacity >>> 16; newCapacity ++; Object[] newArray = Arrays.copyOf(oldArray, newCapacity); Arrays.fill(newArray, oldCapacity, newArray.length, UNSET); newArray[index] = value; indexedVariables = newArray; }
remove
把线程本地变量的删除,避免内存泄露。
public static void remove() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { ((FastThreadLocalThread) thread).setThreadLocalMap(null); } else { slowThreadLocalMap.remove(); } }
destroy
ThreadLocal
的删除,避免内存泄露。
public static void destroy() { slowThreadLocalMap.remove(); }
FastThreadLocalThread
FastThreadLocalThread 继承 Thread ,新添加 InternalThreadLocalMap 这个重要的类变量。
private final boolean cleanupFastThreadLocals;//标志位,等线程有机会执行时,设置为true private InternalThreadLocalMap threadLocalMap;
FastThreadLocal
是 ThreadLocal 特殊的变体,在内部,FastThreadLocal 使用数组中的索引值查找变量,而非通过哈希表查找。这使它比使用哈希表查找具有轻微的性能优势,而且在频繁访问时非常有用。需要和 FastThreadLocalThread 配合使用才能发挥最高性能,Netty 提供 DefaultThreadFactory 工厂类创建 FastThreadLocalThread 线程。
//默认是0
,也是将自己放在InternalThreadLocalMap
的数组indexedVariables
里的,是将来用于删除的。所以正常存储数据的索引是从1
开始的
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private final int index;//数组索引值,它决定这个FastThreadLocal对象在数组的索引位置 public FastThreadLocal() {
// 构造器初始化时从 InternalThreadLocalMap 获取, InternalThreadLocalMap 内部维护一个 AtomicInteger 对象 index = InternalThreadLocalMap.nextVariableIndex(); }
variablesToRemoveIndex 是采用 static final 修饰的变量,在 FastThreadLocal 初始化时 variablesToRemoveIndex 被赋值为 0。用来存储一个 Set 集合,记录已创建的 FastThreadLocal 对象,方便在 removeAll()
方法中只需要遍历 Object[0] 的 Set 集合找到对应的数据并删除。所以InternalThreadLocalMap 的 value 数据从下标为 1 的位置开始存储。
set
如果值不是UNSET
,就获取InternalThreadLocalMap
,然后setKnownNotUnset
设置,否则就remove
删除。
public final void set(V value) { // #1 先判断是否为初始值 if (value != InternalThreadLocalMap.UNSET) { // #2 非初始值,获取「InternalThreadLocalMap」对象 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // #3 如果存在旧值,则覆盖,如果是新值(即UNSET),在覆盖完之后需要添加到Object[0]的Set集合中 setKnownNotUnset(threadLocalMap, value); } else { // #4 初始值,直接移除即可 remove(); } }
setKnownNotUnset
将索引和值设置进threadLocalMap
里,返回true表示第一次设置,调用addToVariablesToRemove
将FastThreadLocal
添加到删除集合里。
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) { if (threadLocalMap.setIndexedVariable(index, value)) {//新添加的,而不是更新 addToVariablesToRemove(threadLocalMap, this);//需要添加到删除的set里 } }
addToVariablesToRemove
获取删除集合,如果不存在就根据IdentityHashMap
创建一个set
集合,IdentityHashMap
只根据引用地址判断时是不是同一个。然后将set
集合放入threadLocalMap
数组的0
索引位置,将FastThreadLocal
放进set
集合。
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);//获取删除set集合 Set<FastThreadLocal<?>> variablesToRemove;//定义set集合 if (v == InternalThreadLocalMap.UNSET || v == null) {//如果set为空的话,就根据map创建一个 variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>()); threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);//将set添加到threadLocalMap里 } else { variablesToRemove = (Set<FastThreadLocal<?>>) v; } variablesToRemove.add(variable);//将FastThreadLocal添加到set集合 }
remove
删除尝试获取的InternalThreadLocalMap
。
public final void remove() { remove(InternalThreadLocalMap.getIfSet()); }
将当前FastThreadLocal
对象从set
集合里删除,并把数组位置上的对象删除,设置回UNSET
。这里的onRemoval
不一定会执行。
public final void remove(InternalThreadLocalMap threadLocalMap) { if (threadLocalMap == null) { return; } Object v = threadLocalMap.removeIndexedVariable(index);//获取删除的对象,也可能是UNSET removeFromVariablesToRemove(threadLocalMap, this);//从set集合中删除当前FastThreadLocal if (v != InternalThreadLocalMap.UNSET) {//不是UNSET才处理 try { onRemoval((V) v);//不一定能触发的方法,空实现 } catch (Exception e) { PlatformDependent.throwException(e); } } }
removeFromVariablesToRemove
将当前的FastThreadLocal
从set
里删除。
private static void removeFromVariablesToRemove( InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);//获取set集合 if (v == InternalThreadLocalMap.UNSET || v == null) {//还没初始化 return; } @SuppressWarnings("unchecked") Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v; variablesToRemove.remove(variable);//从set中删除 }
get
根据index
获取InternalThreadLocalMap
,获取值,如果不是UNSET
就返回,否则返回初始化的值,默认null
。
public final V get() {
//获取「InternalThreadLocalMap」对象,底层会根据线程类型采取不同策略
//如果是「FastThreadLocalThread」,直接从「FastThreadLocal」对象内存获取即可
//如果是「Thread」,创建new ThreadLocal<InternalThreadLocalMap>()的对象,初始化后返回 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); Object v = threadLocalMap.indexedVariable(index);//根据索引值获取值 if (v != InternalThreadLocalMap.UNSET) {//InternalThreadLocalMap内部存储元素的数据初始值都等于InternalThreadLocalMap.UNSET return (V) v; } return initialize(threadLocalMap);//如果为初始化,需要将初始值改为NULL(当然你也可以重写initialValue()方法返回一个默认值),并且添加到Object[0]的set集合中 }
initialize
设置初始值然后放进threadLocalMap,添加FastThreadLocal
到set
集合中。
private V initialize(InternalThreadLocalMap threadLocalMap) { V v = null; try { v = initialValue();//默认null } catch (Exception e) { PlatformDependent.throwException(e); } threadLocalMap.setIndexedVariable(index, v); addToVariablesToRemove(threadLocalMap, this); return v; } protected V initialValue() throws Exception { return null; }
getIfExists
调用InternalThreadLocalMap
的getIfSet
获取threadLocalMap
,如果获取到了并且值不为UNSET
就返回index
对应的值,否则就null
。因为初始化的时候值都是UNSET
,如果没有设置过就获取,得到的就是UNSET
,所以也要返回null
。
public final V getIfExists() { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); if (threadLocalMap != null) { Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { return (V) v; } } return null; }
destroy
非FastThreadLocalThread
线程的时候要调用,把不用得ThreadLocal
删除,不然可能内存泄露了。
public static void destroy() { InternalThreadLocalMap.destroy(); }
removeAll
在其它容器环境中,可以将FastThreadLocal
全部删除。
public static void removeAll() { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); if (threadLocalMap == null) { return; } try { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);//获取set集合 if (v != null && v != InternalThreadLocalMap.UNSET) { @SuppressWarnings("unchecked") Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v; FastThreadLocal<?>[] variablesToRemoveArray = variablesToRemove.toArray(new FastThreadLocal[0]);//将set转换成数组 for (FastThreadLocal<?> tlv: variablesToRemoveArray) { tlv.remove(threadLocalMap);//每个FastThreadLocal都删除 } } } finally { InternalThreadLocalMap.remove();//将InternalThreadLocalMap删除 } }
removeAll可以在线程执行完了的时候调用,把里面的线程本地变量都释放了。
总结
我们对比介绍了 ThreadLocal 和 FastThreadLocal,简单总结下 FastThreadLocal 的优势。
- 高效查找。FastThreadLocal 在定位数据的时候可以直接根据数组下标 index 获取,时间复杂度 O(1)。而 JDK 原生的 ThreadLocal 在数据较多时哈希表很容易发生 Hash 冲突,线性探测法在解决 Hash 冲突时需要不停地向下寻找,效率较低。此外,FastThreadLocal 相比 ThreadLocal 数据扩容更加简单高效,FastThreadLocal 以 index 为基准向上取整到 2 的次幂作为扩容后容量,然后把原数据拷贝到新数组。而 ThreadLocal 由于采用的哈希表,所以在扩容后需要再做一轮 rehash。
- 安全性更高。JDK 原生的 ThreadLocal 使用不当可能造成内存泄漏,只能等待线程销毁。在使用线程池的场景下,ThreadLocal 只能通过主动检测的方式防止内存泄漏,从而造成了一定的开销。然而 FastThreadLocal 不仅提供了 remove() 主动清除对象的方法,而且在线程池场景中 Netty 还封装了 FastThreadLocalRunnable,FastThreadLocalRunnable 最后会执行 FastThreadLocal.removeAll() 将 Set 集合中所有 FastThreadLocal 对象都清理掉,