Netty4.1源码阅读——前传(FastThreadLocal)

 2023-01-29
原文作者:飞天御剑流 原文地址:https://juejin.cn/post/6932740579365224462

前言

上一篇文章分析了netty将对象进行回收利用,而内部没用使用JAVA的ThreadLoacl,而是使用了FastThreadLocal来保存变量,FastThreadLocal其Fast在哪里,一看源码便知。

正文

ThreadLocal原理

首先看一下传统ThreadLocal实现细节

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
     ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    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);
            }

这里会获取当线程作为参数,通过getMap方法获取一个ThreadLocalMap,ThreadLocalMap其实就是线程的一个变量,而通过getEntry方法可以看出来是使用的HashCode的方法来获取Entry;使用hashMap的方法有两个开销:计算hash和hash冲突

这里初步怀疑FastThreadLocal是在Hash上面做文章,只有这样能让ThreadLocal变快

FastThreadLocal原理

    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
    
    private final int index;
    
        public FastThreadLocal() {
            index = 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;
        }

首先内部有一个静态方法和普通变量都调用了nextVariableIndex()方法,这是一个自增的索引,variablesToRemoveIndex全局唯一值为0,index根据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);
        }

首先是获取InternalThreadLocalMap,调用了静态的get方法

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

使用FastThreadLocalThread线程创建FastThreadLocal才会获得加速效果,如果是普通线程则和ThreadLoacl没什么区别

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
            InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
            if (threadLocalMap == null) {
                thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
            }
            return threadLocalMap;
        }
    
    private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
                new ThreadLocal<InternalThreadLocalMap>();
    
    
    private static InternalThreadLocalMap slowGet() {
            InternalThreadLocalMap ret = slowThreadLocalMap.get();
            if (ret == null) {
                ret = new InternalThreadLocalMap();
                slowThreadLocalMap.set(ret);
            }
            return ret;
        }

可以看到,如果当前线程是FastThreadLocalThread,线程重写了threadLocalMap方法,直接就获取到了InternalThreadLocalMap,如果没有就new一个出来;如果是普通线程,还是调用了ThreadLocal

ThreadLocalMap是个什么?看这一段代码

    Object v = threadLocalMap.indexedVariable(index);
    
    
    public Object indexedVariable(int index) {
            Object[] lookup = indexedVariables;
            return index < lookup.length? lookup[index] : UNSET;
        }

indexedVariable竟然是通过数组索引获取元素,数组的速度要比Map快很多;而这个index前面讲过,每new一个FastThreadLocal,就会自增1,同一个线程使用多个FastThreadLocal,每一个实例都对应了数组的一个下标,所以支持快速访问

    private V initialize(InternalThreadLocalMap threadLocalMap) {
            V v = null;
            try {
                v = initialValue();
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
    
            threadLocalMap.setIndexedVariable(index, v);
            addToVariablesToRemove(threadLocalMap, this);
            return v;
        }
    
    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;
            }
        }
    
    //扩容
    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;
        }

initialValue是交给用户重写的方法,当get出来一个null值的时候会初始化,然后设置数组的下标,如果空间不够会进行扩容

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            Set<FastThreadLocal<?>> variablesToRemove;
            if (v == InternalThreadLocalMap.UNSET || v == null) {
                variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
                threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
            } else {
                variablesToRemove = (Set<FastThreadLocal<?>>) v;
            }
    
            variablesToRemove.add(variable);
        }

由于FastThreadLocal有一个静态变量variablesToRemoveIndex,其值为0;作用是在Map的数组的起始位置新建一个Set来保存所有FastThreadLocal实例

总结

想要实现加速效果,创建的线程必须是FastThreadLocalThread,这个线程内部保存了InternalThreadLocalMap实例,而这个Map内部使用了数组作为底层存储,当一个线程有多个FastThreadLocal的时候,每个FTL使用index去访问数组,相比于ThreadLocal使用的HashMap方法减少了Hash碰撞;就算多个线程修改同一个FastThreadLocal实例,内部保存的元素也是线程唯一的,并不会因为多线程导致一致性问题。