• 池化内存分配三


    池化内存分配三

    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 对象都清理掉,

     

  • 相关阅读:
    BZOJ 1911: [Apio2010]特别行动队 斜率优化dp
    BZOJ 2751: [HAOI2012]容易题(easy) 数学
    Wunder Fund Round 2016 (Div. 1 + Div. 2 combined) B. Guess the Permutation 水题
    Wunder Fund Round 2016 (Div. 1 + Div. 2 combined) A. Slime Combining 水题
    BZOJ 2768: [JLOI2010]冠军调查 最小割
    BZOJ 1497: [NOI2006]最大获利 最小割
    Codeforces Round #140 (Div. 1) D. The table 构造
    ICPC-CAMP day1 D.Around the world
    Codeforces Round #340 (Div. 2) E. XOR and Favorite Number 莫队算法
    BZOJ 2038 [2009国家集训队]小Z的袜子 莫队
  • 原文地址:https://www.cnblogs.com/xiaojiesir/p/15471429.html
Copyright © 2020-2023  润新知