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

304 阅读2分钟

前言

上一篇文章分析了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实例,内部保存的元素也是线程唯一的,并不会因为多线程导致一致性问题。