前言
上一篇文章分析了netty将对象进行回收利用,而内部没用使用JAVA的ThreadLoacl,而是使用了FastThreadLocal来保存变量,FastThreadLocal其Fast在哪里,一看源码便知。
正文
ThreadLocal原理
首先看一下传统ThreadLocal实现细节
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
这里会获取当线程作为参数,通过getMap方法获取一个ThreadLocalMap,ThreadLocalMap其实就是线程的一个变量,而通过getEntry方法可以看出来是使用的HashCode的方法来获取Entry;使用hashMap的方法有两个开销:计算hash和hash冲突
这里初步怀疑FastThreadLocal是在Hash上面做文章,只有这样能让ThreadLocal变快
FastThreadLocal原理
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private final int index;
public FastThreadLocal() {
index = 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;
}
首先内部有一个静态方法和普通变量都调用了nextVariableIndex()方法,这是一个自增的索引,variablesToRemoveIndex全局唯一值为0,index根据FastThreadLocal实例化的个数而自增
看一下get方法
public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
return initialize(threadLocalMap);
}
首先是获取InternalThreadLocalMap,调用了静态的get方法
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}
使用FastThreadLocalThread线程创建FastThreadLocal才会获得加速效果,如果是普通线程则和ThreadLoacl没什么区别
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
new ThreadLocal<InternalThreadLocalMap>();
private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
可以看到,如果当前线程是FastThreadLocalThread,线程重写了threadLocalMap方法,直接就获取到了InternalThreadLocalMap,如果没有就new一个出来;如果是普通线程,还是调用了ThreadLocal
ThreadLocalMap是个什么?看这一段代码
Object v = threadLocalMap.indexedVariable(index);
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
indexedVariable竟然是通过数组索引获取元素,数组的速度要比Map快很多;而这个index前面讲过,每new一个FastThreadLocal,就会自增1,同一个线程使用多个FastThreadLocal,每一个实例都对应了数组的一个下标,所以支持快速访问
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;
}
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 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;
}
initialValue是交给用户重写的方法,当get出来一个null值的时候会初始化,然后设置数组的下标,如果空间不够会进行扩容
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);
}
由于FastThreadLocal有一个静态变量variablesToRemoveIndex,其值为0;作用是在Map的数组的起始位置新建一个Set来保存所有FastThreadLocal实例
总结
想要实现加速效果,创建的线程必须是FastThreadLocalThread,这个线程内部保存了InternalThreadLocalMap实例,而这个Map内部使用了数组作为底层存储,当一个线程有多个FastThreadLocal的时候,每个FTL使用index去访问数组,相比于ThreadLocal使用的HashMap方法减少了Hash碰撞;就算多个线程修改同一个FastThreadLocal实例,内部保存的元素也是线程唯一的,并不会因为多线程导致一致性问题。