写点什么

netty 系列之: 给 ThreadLocal 插上梦想的翅膀, 详解 FastThreadLocal

作者:程序那些事
  • 2022 年 5 月 10 日
  • 本文字数:2833 字

    阅读完需:约 9 分钟

netty系列之:给ThreadLocal插上梦想的翅膀,详解FastThreadLocal

简介

JDK 中的 ThreadLocal 可以通过 get 方法来获得跟当前线程绑定的值。而这些值是存储在 ThreadLocal.ThreadLocalMap 中的。而在 ThreadLocalMap 中底层的数据存储是一个 Entry 数组中的。


那么从 ThreadLocalMap 中获取数据的速度如何呢?速度有没有可以优化的空间呢?


一起来看看。

从 ThreadLocalMap 中获取数据

ThreadLocalMap 作为一个 Map,它的底层数据存储是一个 Entry 类型的数组:


private Entry[] table;
复制代码


我们再来回顾一下 ThreadLocal 是怎么获取数据的:


        private Entry getEntry(ThreadLocal<?> key) {            int i = key.threadLocalHashCode & (table.length - 1);            Entry e = table[i];            if (e != null && e.get() == key)                return e;            else                return getEntryAfterMiss(key, i, e);        }
复制代码


首先根据 ThreadLocal 对象中的 threadLocalHashCode 跟 table 的长度进行取模运算,得到要获取的 Entry 在 table 中的位置,然后判断位置 Entry 的 key 是否和要获取的 ThreadLocal 对象一致。


如果一致,说明获取到了 ThreadLocal 绑定的对象,直接返回即可。


如果不一致,则需要再次进行查找。


我们看下再次查找的逻辑:


        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {            Entry[] tab = table;            int len = tab.length;
while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
复制代码


getEntryAfterMiss 的逻辑是,先判断 Entry 中的对象是否要获取的对象,如果是则直接返回。


如果 Entry 中的对象为空,则触发清除过期 Entry 的方法。否则的话计算出下一个要判断的地址,再次进行判断,直到最终找到要找到的对象为止。


可以看到,如果第一次没有找到要找到的对象的话,后面则可能会遍历多次,从而造成执行效率变低。


那么有没有可以提升这个寻找速度的方法呢?答案是肯定的。

FastThreadLocal

之前我们提到了,Netty 中的本地对象池技术,netty 为其创建了一个专门的类叫做 Recycler。虽然 Recycler 中也使用到了 ThreadLocal,但是 Recycler 使用的 threadLocal 并不是 JDK 自带的 ThreadLocal,而是 FastThreadLocal。和它关联的 ThreadLocalMap 叫做 InternalThreadLocalMap,和它关联的 Thread 叫做 FastThreadLocalThread。netty 中的类和 JDK 中的类的对应关系如下:



我们先来看 FastThreadLocalThread。不管它到底快不快,既然是 Thread,那么自然就要继承自 JDK 的 Thread:


public class FastThreadLocalThread extends Thread
复制代码


和 Thread 一样,FastThreadLocalThread 中也有一个 ThreadLocalMap,叫做 InternalThreadLocalMap,它是 FastThreadLocalThread 的 private 属性:


private InternalThreadLocalMap threadLocalMap;
复制代码


InternalThreadLocalMap 中也有一个 ThreadLocal 对象,叫做 slowThreadLocalMap,是在 fastThreadLocalMap 不生效的时候使用的。


接下来我们来看下这个 ThreadLocalMap 为什么快:


    public static InternalThreadLocalMap get() {        Thread thread = Thread.currentThread();        if (thread instanceof FastThreadLocalThread) {            return fastGet((FastThreadLocalThread) thread);        } else {            return slowGet();        }    }
复制代码


从 get 方法可以看到,如果当前 thread 是 FastThreadLocalThread 的话,则会去调用 fastGet 方法,否则调用 slowGet 方法。


slowGet 方法就是使用传统的 ThreadLocal 来 get:


    private static InternalThreadLocalMap slowGet() {        InternalThreadLocalMap ret = slowThreadLocalMap.get();        if (ret == null) {            ret = new InternalThreadLocalMap();            slowThreadLocalMap.set(ret);        }        return ret;    }
复制代码


我们重点关注下 fastGet 方法:


    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();        if (threadLocalMap == null) {            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());        }        return threadLocalMap;    }
复制代码


这里 fast 的效果就出现了,fastGet 直接返回了 thread 中的 InternalThreadLocalMap 对象,不需要进行任何查找的过程。


再看下 FastThreadLocal 如何使用 get 方法来获取具体的值:


    public final V get() {        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();        Object v = threadLocalMap.indexedVariable(index);        if (v != InternalThreadLocalMap.UNSET) {            return (V) v;        }
return initialize(threadLocalMap); }
复制代码


可以看到 FastThreadLocal 中的 get 首先调用了 InternalThreadLocalMap 的 get 方法,直接返回了 FastThreadLocalThread 中的 InternalThreadLocalMap 对象,这个速度是非常快的。


然后直接使用 FastThreadLocal 中的 index,来获取 threadLocalMap 中具体存储数据的数组中的元素:


    public Object indexedVariable(int index) {        Object[] lookup = indexedVariables;        return index < lookup.length? lookup[index] : UNSET;    }
复制代码


因为是直接 index 访问的,所以也非常快。这就是 fast 的由来。


那么有同学会问题了,FastThreadLocal 中的 index 是怎么来的呢?


    private final int index;
public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); }
复制代码


而 InternalThreadLocalMap 中的 nextVariableIndex 方法是一个静态方法:


    public static int nextVariableIndex() {        int index = nextIndex.getAndIncrement();        if (index < 0) {            nextIndex.decrementAndGet();            throw new IllegalStateException("too many thread-local indexed variables");        }        return index;    }
复制代码


也就是说,只要 new 一个 FastThreadLocal,该对象中,就会生成一个唯一的 index。然后 FastThreadLocal 使用该 index 去 InternalThreadLocalMap 中存取对象。这样就不存在 ThreadLocal 那种需要多次遍历查找的情况。

总结

FastThreadLocal 是和 FastThreadLocalThread 配套使用才会真正的 fast,否则的话就会 fallback 到 ThreadLocal 去执行,大家一定要注意这一点。


更多内容请参考 http://www.flydean.com/48-netty-fastthreadlocal/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:给ThreadLocal插上梦想的翅膀,详解FastThreadLocal_Java_程序那些事_InfoQ写作社区