FastThreadLocal源码

Posted walker993

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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二

以上是关于FastThreadLocal源码的主要内容,如果未能解决你的问题,请参考以下文章

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

谈谈FastThreadLocal为啥能这么快?吊打 ThreadLocal!

FastThreadLocal

FastThreadLocal

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?