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的方式也不一样。