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

34次阅读

共计 3929 个字符,预计需要花费 10 分钟才能阅读完成。

简介

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 中的类的对应关系如下:

netty 中的对象 JDK 中的对象
FastThreadLocalThread Thread
InternalThreadLocalMap ThreadLocal.ThreadLocalMap
FastThreadLocal ThreadLocal

咱们先来看 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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

正文完
 0