吃透Netty源码系列二十五之FastThreadLocal一

FastThreadLocal

先说下这个,简单说就是你可以理解为跟线程相关的数据,每个线程都可以有相同key的不同数据。比如netty想让每个线程对应有自己的内存arena,当然也可以让他们共有一块大内存,但是这样会导致很多多线程竞争的问题,所以能不能设计成让每个线程独有自己的内存,尽量跟别的线程少竞争,提高效率,所以可以用ThreadLocal,但是netty嫌效率不够,因此开发了FastThreadLocal,用空间换时间,提高效率。

怎么提高效率

要搞清楚怎么就能提高效率,怎么个用空间换时间,就得先搞清楚ThreadLocal是怎么来处理的。

ThreadLocal的处理方式

这个介绍网上文章太多了,我就简单的说,Thread内部都有一个ThreadLocalMap属性,内部是一个Entry数组,Entry里面保存着ThreadLocal和对应的值,而且是个弱引用,引用对象就是ThreadLocal,这个可以在gc的时候回收ThreadLocal,然后调用相应的方法再对值和Entry进行回收:
在这里插入图片描述
这里比较耗时的地方就是,在放入ThreadLocalMap的时候,用的是hash算法,然后对数组取模算出索引,但是这样可能会冲突,解决冲突的方式叫线性探测法,就是看冲突索引的下一个能不能放,不能就继续往后找,不够就扩容,直到能放下为止。所以一次放的操作就可能消耗很大了。
在这里插入图片描述

FastThreadLocal的处理方式

这个就不一样,他直接就给每个线程设置一个InternalThreadLocalMap,不管是FastThreadLocalThread直接设置,还是JDK的普通线程Thread,设置进ThreadLocalMap间接设置。InternalThreadLocalMap内部维护着一个对象数组和索引,要放进去直接将值放进对象数组,返回一个索引,记录在FastThreadLocal中,取的时候直接拿这个索引去对象数组里取,非常方便。但是这样的一个缺点就是内存消耗比较大,因为只会扩容,而且索引只会递增,这样数组就会越来越大。所以就是空间换时间了。
在这里插入图片描述

UnpaddedInternalThreadLocalMap

这个定义了一些跟线程独有的属性,slowThreadLocalMap 其实就是用了原始的ThreadLocal,但是存的是InternalThreadLocalMap,就是普通线程用FastThreadLocal的时候先创建一个InternalThreadLocalMap放入,然后后面就可以取来用了,这个过程当然比FastThreadLocalThread直接获取InternalThreadLocalMap慢啦。

class UnpaddedInternalThreadLocalMap {
    //从ThreadLocal中获取InternalThreadLocalMap
    static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
    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;//解码器缓存

    // ArrayList-related thread-locals
    ArrayList<Object> arrayList;

    UnpaddedInternalThreadLocalMap(Object[] indexedVariables) {
        this.indexedVariables = indexedVariables;
    }
}

InternalThreadLocalMap

继承了上面那个,但是提供了一系列的操作。功能类似于普通线程的ThreadLocalMap,效率高。
在这里插入图片描述

主要方法

InternalThreadLocalMap

直接创建32个空对象的数组。


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

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

getIfSet

获取当前线程,如果是FastThreadLocalThread就直接获取InternalThreadLocalMap,如果不是就用ThreadLocal获取,获取不到就返回null

    public static InternalThreadLocalMap getIfSet() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return ((FastThreadLocalThread) thread).threadLocalMap();//快速获取
        }
        return slowThreadLocalMap.get();//常规获取
    }

get

获取InternalThreadLocalMap ,如果获取不到就创建一个,如果是FastThreadLocalThread就用快速的方法,否则就慢的方法。

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

fastGet

快方法就是直接获取,获取不到就创建一个设置进去,返回。

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

ThreadLocal中获取,不存在就创建一个,再设置进去,里面可能涉及一堆hash算法,冲突解决,扩容,所以相对就慢啦。

 private static InternalThreadLocalMap slowGet() {
        ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

nextVariableIndex

获取索引值,FastThreadLocal构造的时候需要,因为有索引值才可以从数组中获取值啊。

    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();//获取后自增
        if (index < 0) {//溢出就抛异常了,那也太多了
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

indexedVariable

获取索引对应的值,如果超边界就返回UNSET,表示没设置过值。

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

setIndexedVariable

设置索引和对应的值,如果在范围内,就替换旧值,返回旧值是否是UNSET,是就表示第一次设置,返回true,不是就表示更新,返回false。超出范围就扩容。

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

这段代码在内存分配里的规范容量操作遇到过,扩容到大于index的最小的2的幂次,比如index=32,扩容到64,然后把老的数组拷贝到新的数组里去,不满的地方用UNSET填满。

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

removeIndexedVariable

删除数组中的值并返回,其实就是用UNSET替换了。溢出就返回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;
        }
    }

isIndexedVariableSet

该数组在索引是否有设置了值。

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

remove

把线程本地变量的删除,避免内存泄露。

    public static void remove() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            ((FastThreadLocalThread) thread).setThreadLocalMap(null);
        } else {
            slowThreadLocalMap.remove();
        }
    }

destroy

ThreadLocal的删除,避免内存泄露。

    public static void destroy() {
        slowThreadLocalMap.remove();
    }

size

获取InternalThreadLocalMap已经设置了多少有用对象,不包括UNSET,还数组中的第0个元素set集合,除了对象数组indexedVariables外,还可能会有其他的属性。

 public int size() {
        int count = 0;

        if (futureListenerStackDepth != 0) {
            count ++;
        }
        if (localChannelReaderStackDepth != 0) {
            count ++;
        }
        if (handlerSharableCache != null) {
            count ++;
        }
        if (counterHashCode != null) {
            count ++;
        }
        if (random != null) {
            count ++;
        }
        if (typeParameterMatcherGetCache != null) {
            count ++;
        }
        if (typeParameterMatcherFindCache != null) {
            count ++;
        }
        if (stringBuilder != null) {
            count ++;
        }
        if (charsetEncoderCache != null) {
            count ++;
        }
        if (charsetDecoderCache != null) {
            count ++;
        }
        if (arrayList != null) {
            count ++;
        }

        for (Object o: indexedVariables) {
            if (o != UNSET) {
                count ++;
            }
        }

        // We should subtract 1 from the count because the first element in 'indexedVariables' is reserved
        // by 'FastThreadLocal' to keep the list of 'FastThreadLocal's to remove on 'FastThreadLocal.removeAll()'. set集合为了保证FastThreadLocal能调用removeAll删除所有的removeAll
        return count - 1;//第一个是set集合,不算
    }

本篇主要介绍一下基本的操作和概念,具体还需要自己去调试看下方法怎么怎么执行的,加深理解,光看是没用的。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值