zoukankan      html  css  js  c++  java
  • FastThreadLocal源码

    在Netty中,跟ThreadLocal做用很类似的类。配套使用的还有InternalThreadLocalMap,它对应ThreadLocalMap、FastThreadLocalThread,它对应Thread。对ThreadLocal不熟悉的小伙伴可以参考第三章 对象的共享中的对应部分。我们一起看下源码吧~

    一、重要属性

      1. 变量存放的位置

      在Thread中,使用ThreadLocalMap的Entry[]来保存变量的副本。类似的,在FastThreadLocalThread中,使用InternalThreadLocalMap中的Object[]数组来保存副本:

    class UnpaddedInternalThreadLocalMap {
    
        static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
        static final AtomicInteger nextIndex = new AtomicInteger();
    
    
        /**
         * Used by {@link FastThreadLocal}
         */
        //存放缓存数据的数组,不同的FastThreadLocal存放在不同的下标
        Object[] indexedVariables;
        ...
    }

      UnpaddedInternalThreadLocalMap是InternalThreadLocalMap的父类。

      2. variablesToRemoveIndex常量

      在FastThreadLocal中有一项属性:

    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

      这个变量代表一个下标。ThreadLocalMap中Entry是一个指向ThreadLocal的弱引用,而在InternalThreadLocalMap中,在哪里维护FastThreadLocal的呢?

      答案就是上面讲的indexedVariables数组中,下标为variablesToRemoveIndex的位置,这个下标始终为0。看下取值的方法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;
    }

      因为variablesToRemoveIndex是类常量,在类加载期间就会初始化,所以此时取值为0。至于存储FastThreadLocal的结构下面会讲

      3. index变量

    private final int index;

      每个FastThreadLocal都有一个属性,初始化时赋值,代表的是这个FastThreadLocal变量对应的副本在indexedVariables数组中存放的位置。

    二、重要方法

      1. 初始化方法

    public FastThreadLocal() {
        //实例化阶段赋值,在整个过程中会实例化多次。从1开始递增
        index = InternalThreadLocalMap.nextVariableIndex();
    }

      整个Netty中用到了多个FastThreadLocal变量,而他们是共用一个计数器的,因为实例初始化方法在类加载之后,所以这里从1开始计数。并且每次递增1。

      在ThreadLocalMap中计算存放下表的方法是 int i = key.threadLocalHashCode & (len-1); ,前半部分并不是真正计算哈希值,而是类似这里,使用一个计数器,每次递增,但不是递增1,而是递增0x61c88647,这么设置是为了降低哈希冲突。后半部分是对数组长度取模(对2的整数幂取模的简化方法)才能得到存放的下标,取模就意味着存在哈希冲突的问题,而在InternalThreadLocalMap中不存在哈希冲突。

      2. set方法

     1 public final void set(V value) {
     2     if (value != InternalThreadLocalMap.UNSET) {
     3         //从当前线程中获取存储变量的map
     4         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
     5         setKnownNotUnset(threadLocalMap, value);
     6     } else {
     7         remove();
     8     }
     9 }
    10 
    11 public static InternalThreadLocalMap get() {
    12     Thread thread = Thread.currentThread();
    13     if (thread instanceof FastThreadLocalThread) {
    14         return fastGet((FastThreadLocalThread) thread);
    15     } else {
    16         return slowGet();
    17     }
    18 }
    19 
    20 
    21 private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
    22     InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
    23     if (threadLocalMap == null) {
    24         thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
    25     }
    26     return threadLocalMap;
    27 }

      行2, public static final Object UNSET = new Object(); ,这个常量是用来填充的indexedVariables数组空白部分的。

      行4,这个是从当前线程(FastThreadLocalThread)的threadLocalMap属性中拿到InternalThreadLocalMap。

      我们看下行5的 setKnownNotUnset 方法:

    private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        //这个index在实例化时赋值
        if (threadLocalMap.setIndexedVariable(index, value)) {
            //维护一个key的集合,当每次新增时,将key塞进入。更新时不用,因为key没变
            addToVariablesToRemove(threadLocalMap, this);
        }
    }
    
    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 static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        //根据指定下标,拿出维护的key集合
        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;
        }
        //将新key添加进去
        variablesToRemove.add(variable);
    }

      这个方法做了两件事,一是将副本存放到数组对应位置,如果下标越界会自动扩容。二是将FastThreadLocal存放到数组下标为0的位置,存放的结构是一个Set。

      3. get方法

    public final V get() {
        //从当前线程中取出map
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        //从对应位置找到副本
        Object v = threadLocalMap.indexedVariable(index);
        //不是默认值就返回
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }
        //如果是默认值,这里会进行一个set(null)的操作
        return initialize(threadLocalMap);
    }
    
    private V initialize(InternalThreadLocalMap threadLocalMap) {
        V v = null;
        try {
            //默认是null,protected级别,可以自定义实现
            v = initialValue();
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }
        //在数组对应位置设置v
        threadLocalMap.setIndexedVariable(index, v);
        //将key添加到集合
        addToVariablesToRemove(threadLocalMap, this);
        return v;
    }

      有意思的一点是,如果取出的是默认值UNSET,代表这个FastThreadLocal对应的副本还未设置过,此时会自动设置为null。

      4. remove方法

    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }
        //从数组中移除对应下标的元素,其实是用UNSET覆盖
        Object v = threadLocalMap.removeIndexedVariable(index);
        //从key集合中移除对应的key
        removeFromVariablesToRemove(threadLocalMap, this);
    
        if (v != InternalThreadLocalMap.UNSET) {
            try {
                //这个方法是一个空实现,可以继承然后自定义一些后续操作
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }
    
    public Object removeIndexedVariable(int index) {
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object v = lookup[index];
            //将对应位置设置为默认值
            lookup[index] = UNSET;
            return v;
        } else {
            return UNSET;
        }
    }
    
    private static void removeFromVariablesToRemove(
            InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        //取出key的集合
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
    
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            return;
        }
    
        @SuppressWarnings("unchecked")
        Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
        //从集合中删除key
        variablesToRemove.remove(variable);
    }

    三、总结

      总体上讲,和ThreadLocal还是很类似的,区别是不存在不易察觉的弱引用导致的内存泄露问题,当然也需要手动remove。不存在哈希冲突的问题,存放key的方式也不一样。

    参考:

    吃透Netty源码系列二十六之FastThreadLocal二

    人生就像蒲公英,看似自由,其实身不由己。
  • 相关阅读:
    mysql日期转化
    跳来跳去,到底去大公司还是小公司?
    年度重大升级,IntelliJ IDEA 2019.2 稳定版发布
    3年Java,鏖战腾讯
    重磅!挑战Oracle,华为将开源 GaussDB 数据库
    厉害了,Java EE 再次更名为 Jakarta EE
    彻底干掉恶心的 SQL 注入漏洞, 一网打尽!
    PostgreSQL 荣获 2019 年 O'Reilly 终身成就奖
    Intellij IDEA 智能补全的 10 个姿势,简直不能太牛逼!
    注意,千万不要打断工作中的程序员
  • 原文地址:https://www.cnblogs.com/walker993/p/14897918.html
Copyright © 2011-2022 走看看