池化内存分配三

池化内存分配三

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表示第一次设置,调用addToVariablesToRemoveFastThreadLocal添加到删除集合里。

    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

将当前的FastThreadLocalset里删除。

 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,添加FastThreadLocalset集合中。

  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

调用InternalThreadLocalMapgetIfSet获取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 对象都清理掉,

 

原文地址:https://www.cnblogs.com/xiaojiesir/p/15471429.html