netty5笔记-concurrent-FastThreadLocal

并发的一个很大的朋友就是锁,因为锁可以在并发的情况下保护一些公共的变量;并发的一个很大的敌人也是锁,因为锁带来了很大的性能开销。

很多时候我们必须加锁来应对并发带来的线程安全问题,而ThreadLocal则给我们开辟了另外一个思路,将数据保存在线程的私有字段中,使一个线程无法读到其他线程的数据, 这样的数据存储方式,自然就线程安全。 那么ThreadLocal本身还有没有可以挖掘的优化点呢,netty告诉你,有!

我们知道ThreadLocal的实现是在Thread里放一个类似map(虽然叫map,但实际上是纯数组实现)的数据结构来存数据,而这个自定义的map要查找一个元素主要分成两步:1、通过元素的hashcode计算得到对应的index,判断对应index上是否有数据,如果有数据且key==yourThreadLocal则匹配成功;2、否则按顺序依次挪到下一个位置进行对比,直到找到yourThreadLocal。 这样查询一个元素可能需要很多步。同样插入或移除一个值,操作步数也比较多。

下面来看看netty君是怎么做的,首先基本思路和jdk的ThreadLocal相同,在一个线程上增加一个私有字段存数据。然而原有的ThreadLocal并没有提供定制的接口,咋办呢?netty继承Thread实现了一个FastThreadLocalThread,该FastThreadLocalThread实现了FastThreadLocalAccess接口:

 

public interface FastThreadLocalAccess {
    InternalThreadLocalMap threadLocalMap();
    void setThreadLocalMap(InternalThreadLocalMap threadLocalMap);
}

该接口暴露两个方法用来获取和设置InternalThreadLocalMap , InternalThreadLocalMap就是数据存储的实现了。 从构造方法可以看到InternalThreadLocalMap与ThreadLocalMap一样都采取了数组的方式存储元素。

 

 

    private InternalThreadLocalMap() {
        super(newIndexedVariableTable());
    }

    private static Object[] newIndexedVariableTable() {
        Object[] array = new Object[32];
        Arrays.fill(array, UNSET);
        return array;
    }

 

InternalThreadLocalMap根据netty的实际情况还加入了一些其他的字段做一些定制化的的数据缓存,这样在ThreadLocal的基础上功能又增强了。不过话说回来ThreadLocal毕竟是公共的解决方案,不太可能放这些乱七八糟的东西。来看看这些netty引入的字段:

 

    static ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap;
    static final AtomicInteger nextIndex = new AtomicInteger();
    /** Used by {@link FastThreadLocal} */
    Object[] indexedVariables;

    // Core thread-locals
    int futureListenerStackDepth;
    int localChannelReaderStackDepth;
    Map<Class<?>, Boolean> handlerSharableCache;
    IntegerHolder counterHashCode;
    ThreadLocalRandom random;
    Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache;
    Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache;

    // String-related thread-locals
    StringBuilder stringBuilder;
    Map<Charset, CharsetEncoder> charsetEncoderCache;
    Map<Charset, CharsetDecoder> charsetDecoderCache;

indexedVariables相关方法都是采用直接操作index的方式,效率极高:

 

    // 直接根据index获取
    public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length? lookup[index] : UNSET;
    }

    /**
     * @return {@code true} if and only if a new thread-local variable has been created
     */
    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 {
            // 如果要查询的index不在indexedVariables范围,则需要先扩展在设置           
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

    private void expandIndexedVariableTableAndSet(int index, Object value) {
        Object[] oldArray = indexedVariables;
        final int oldCapacity = oldArray.length;
        // newCapacity-> 32,64,128,256,512....
       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方法直接在对应位置上设置UNSET
    public Object removeIndexedVariable(int index) {
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object v = lookup[index];
            lookup[index] = UNSET;
            return v;
        } else {
            return UNSET;
        }
    }

    public boolean isIndexedVariableSet(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length && lookup[index] != UNSET;
    }

这段代码简单得紧,所有操作都是直接的index查找,这样的话netty的FastThreadLocal元素操作就只有一步。然而这个index是如何得来的? 答案就是上面出现过的nextIndex,注意nextIndex是静态的,因为ThreadLocal正确的用法就是声明成static。 在整个进程中可能存在很多个ThreadLocal,对于jdk的ThreadLocal,识别ThreadLocal的方式是引用的对比,即key == yourThreadLocal的方式。而netty实现的FastThreadLocal则采用全局序列号的方式,采用AtomicInteger分配可以保证index的唯一性,同时index从0开始递增,符合数据的下标模式,因此FastThreadLocal采用的index就可以和数组完美的结合, 相比ThreadLocal查找一个元素还需要考虑冲突的情况来说,的确是更先进。

 

知道了上面这一段原理的介绍,后面的代码已经不重要了,不过我们还是来八一八吧,时间不多的同学可以选择不往下看了(虽然后面还有一个重要的点)。

先看看几个辅助的方法:

 

    // 该方法与get()不同的是,get()在查找不到数据是会进行初始化,而这个方法则是直接返回
    public static InternalThreadLocalMap getIfSet() {
        Thread thread = Thread.currentThread();
        InternalThreadLocalMap threadLocalMap;
        if (thread instanceof FastThreadLocalAccess) {
            threadLocalMap = ((FastThreadLocalAccess) thread).threadLocalMap();
        } else {
            ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
            if (slowThreadLocalMap == null) {
                threadLocalMap = null;
            } else {
                threadLocalMap = slowThreadLocalMap.get();
            }
        }
        return threadLocalMap;
    }

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalAccess) {
            // 如果实现了FastThreadLocalAccess,则采用netty的算法
            return fastGet((FastThreadLocalAccess) thread);
        } else {
            // 没有未实现FastThreadLocalAccess,则降级使用jdk的ThreadLocal来存放InternalThreadLocalMap
            return slowGet();
        }
    }

    private static InternalThreadLocalMap fastGet(FastThreadLocalAccess thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

    private static InternalThreadLocalMap slowGet() {
        // slowGet采用了java的ThreadLocal来管理InternalThreadLocalMap;
        // 整个方案相对于直接用ThreadLocal来说又多了一步InternalThreadLocalMap的查询,但差距很小。
        // 好处是统一了FastThreadLocal的模型,同时上文中也提到InternalThreadLocalMap做了很多功能上的增强。
        ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
        if (slowThreadLocalMap == null) {
            UnpaddedInternalThreadLocalMap.slowThreadLocalMap =
                    slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
        }

        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

看完了辅助方法,我们回到FastThreadLocal看看具体的实现:

 

    private final int index;

    public FastThreadLocal() {
        // 初始化时分配一个全局唯一的index
        index = InternalThreadLocalMap.nextVariableIndex();
    }

    public final V get() {
        // InternalThreadLocalMap.get()获取到与当前线程关联的InternalThreadLocalMap, 通过该map来查询具体数据
       return get(InternalThreadLocalMap.get());
    }

    public final V get(InternalThreadLocalMap threadLocalMap) {
        // 直接采用index下标访问threadLocalMap中数组的指定位置元素
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }
        // 不存在值则初始化
        return initialize(threadLocalMap);
    }

    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;
    }

    /**
     * Set the value for the current thread.
     */
    public final void set(V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            set(InternalThreadLocalMap.get(), value);
        } else {
            remove();
        }
    }

    /**
     * Set the value for the specified thread local map. The specified thread local map must be for the current thread.
     */
    public final void set(InternalThreadLocalMap threadLocalMap, V value) {
       // UNSET表示移除 
       if (value != InternalThreadLocalMap.UNSET) {
            if (threadLocalMap.setIndexedVariable(index, value)) {
                addToVariablesToRemove(threadLocalMap, this);
            }
        } else {
            remove(threadLocalMap);
        }
    }

    /**
     * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue().
     */
    public final void remove() {
        remove(InternalThreadLocalMap.getIfSet());
    }

    /**
     * Sets the value to uninitialized for the specified thread local map;
     * a proceeding call to get() will trigger a call to initialValue().
     * The specified thread local map must be for the current thread.
     */
    @SuppressWarnings("unchecked")
    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }
        // 直接使用下标进行remove
        Object v = threadLocalMap.removeIndexedVariable(index);
        removeFromVariablesToRemove(threadLocalMap, this);

        if (v != InternalThreadLocalMap.UNSET) {
            try {
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }

上面的方法都比较简单,不用再详细的解释,需要注意的是addToVariablesToRemove、removeFromVariablesToRemove的实现:

 

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

    private static void removeFromVariablesToRemove(
            InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {

        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);

        if (v == InternalThreadLocalMap.UNSET || v == null) {
            return;
        }

        @SuppressWarnings("unchecked")
        Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
        variablesToRemove.remove(variable);
    }

从上面的代码可以看到variablesToRemoveIndex占用了一个下标, 该下标的元素是一个包装了IndentityHashMap的Set, 每次FastThreadLocal中设置值的时候将自己加到该Set,移除值的时候将自己从该Set移除。也就是说初始值、设置值、删除值这几个功能的耗时比有值时的get更多。那么为什么要加这个功能?来看看该Set的使用场景:

 

 

    /**
     * 移除绑定到该线程的所有变量. 当不希望保留你无法管理的本地变量时使用该方法做清理。
     */
    public static void removeAll() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
        if (threadLocalMap == null) {
            return;
        }

        try {
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                // 取出Set中的所有FastThreadLocal进行清理
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]);
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            InternalThreadLocalMap.remove();
        }
    }

比如在web server中,classloader可能会开启热加载功能,在重新加载一个类的时候老的数据必须做清理,否则会造成内存泄露。 removeAll提供了这样一个清理数据的方式。 为了这个功能,FastThreadLocal可能会变得不是那么Fast,按我简单的想法,应该提供一个参数来打开它,这样只有需要的应用才打开,而默认情况下FastThreadLocal就真正的Fast了。然而其实这点性能的损失很小,是可以接受的。另一方面,如果你看了jdk的ThreadLocal.remove(),你会发现也! 很! 慢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值