FastThreadLocal相比较于ThreadLocal在FastThreadLocalThread有更好的表现,因为在FastThreadLocal是使用数组而不是像ThreadLocal那样使用hash code 以及hash table去查找对象。尽管看起来非常微妙,但它比使用哈希表产生了一些性能优势,并且在频繁访问时非常有用。
为了使用FastThreadLocal带来的优势,你的线程类型应该使用FastThreadLocal以及它的子类,而不是使用Thread(下面会讲到原因)。因此在Netty中,所有的线程创建都是通过DefaultThreadFactory,因为其默认返回FastThreadLocalThread。
1、FastThreadLocal使用方法
在之前讲到Netty的Recycler的时候,用到了FastThreadLocal存储为其他线程回收的对象,也就是域FastThreadLocal<Map<Stack<?>,WeakOrderQueue>> DELAYED_RECYCLED
private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
@Override
protected Map<Stack<?>, WeakOrderQueue> initialValue() {
return new WeakHashMap<Stack<?>, WeakOrderQueue>();
}
};
从这里可以看到FastThreadLocal本身不是直接实现这个对象,而是通过继承FastThreadLocal并且实现内部的initalValue()方法,在没有对象返回时创建对象。
2、InternalThreadLocalMap和FastThreadLocal内部联系
ThreadLocal本身只是一个窗口,用于访问Thread内部自己的ThreadLocalMap。而FastThreadLocal也是一样,用于访问FastThreadLocalThread内部的InternalThreadLocalMap,所以我们在开始讲解FastThreadLocal前,得先开始介绍InternalThreadLocalMap这个类在FastThreadLocal的使用。
/* FastThreadLocal */
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
每个FastThreadLocal在创建的时候都会有一个index的域,该值会在创建FastThreadLocal时通过c.nextVariableIndex()方法返回生成。
/* InternalThreadLocalMap */
static final AtomicInteger nextIndex = new AtomicInteger();
public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
nextIndex这个是在InternalThreadLocalMap类的一个静态属性,所以该值为JVM中所有InternalThreadLocalMap共享。这也可以说明在InternalThreadLocalMap.nextVariableIndex()返回给FastThreadLocal的index值是全局递增的。
随后通过FastThreadLocal.get()方法获取属性时,会在InternalThreadLocalMap内部的数组指定的下标index获取。
/* FastThreadLocal */
public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
return initialize(threadLocalMap);
}
因为是通过数组下标直接获取对象,比起ThreadLocal内部需要使用hash的方式节约了不小的时间。
3、InternalThreadLocalMap源码解析
简单解释了InternalThreadLocalMap和FastThreadLocal的关系,我们看是讲解下InternalThreadLocalMap的源码。
InternalThreadLocalMap本身继承了UnpaddedInternalThreadLocalMap,UnpaddedInternalThreadLocalMap本身提供了Object类型的数组,在已知存储对象对应下标的情况下可以直接获取到对象。
class UnpaddedInternalThreadLocalMap {
static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
static final AtomicInteger nextIndex = new AtomicInteger();
/** Used by {@link FastThreadLocal} */
Object[] indexedVariables;
...
}
上面我们看到了可以在FastTheadLocal中,通过InternalTheadLocalMap.get()静态方法返回存储在一个InternalThreadLocalMap的实例。其内部实现为
/* InternalThreadLocalMap */
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}
get的方法会根据当前线程的是否继承了FastThreadLocalThred采取fastGet和slowGet两种方式。我们先来看fastGet这种
/* InternalThreadLocalMap */
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
fastGet()就是比较简单的从FastThreadLocalThread中直接返回InternalThreadLocalMap,并且判断该值为null再初始化。现在我们再来看下slowGet的情况
/* UnpaddedInternalThreadLocalMap */
static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
/* InternalThreadLocalMap */
private static InternalThreadLocalMap slowGet() {
ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
slowGet()的调用是发生在线程本身没有继承FastThreadLocalThread的情况下,是原生的Thread类型下,此时slowGet()就会去原生线程的ThreadLocal中获取slowThreadLocalMap域。
所以我们可以就此得出InternalThreadLocalMap中slowGet()和fastGet()区别在于,fastGet()的使用场景是当前线程是FastThreadLocalThread的,所以可以直接返回InternalThreadLocalMap这个域;而slowGet()是从ThreadLocal中返回的InternalThreadLocalMap,需要通过hash计算以及存储时的hash碰撞,所以相比较会慢一些。
既然获取到InternalThreadLocalMap,那么就可以通过创建FastThreadLocal分发的index获取到存储在其中的对象。
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
简单看出:InternalTheadLocalMap改用数组的方式替换了ThreadLocalMap中用Hash计算的问题,并且避免了Hash碰撞带来的影响。
3、FastThreadLocalThread源码解析
FastThreadLocalThread本身继承了Thread,所以可以当作普通线程一样去使用。在这里它和普通Thread的区别在于内置了一个InternalThreadLocalMap,这也是FastThreadLocal获取InternalThreadLocalMap中fastGet和slowGet的区别。
public class FastThreadLocalThread extends Thread {
private InternalThreadLocalMap threadLocalMap;
...
public final InternalThreadLocalMap threadLocalMap() {
return threadLocalMap;
}
public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
this.threadLocalMap = threadLocalMap;
}
}
4、FastThreadLocal源码解析
FastThreadLocal作为Netty首选的线程的本地存储,主要用于搭配InternalThreadLocalMap。其实无论时FastTheadLocal还是ThreadLocal,它本身其实只是一个去访问线程内置的线程本地存储的窗口。例如TheadLocal,其实是访问Thead内置的ThreadLocalMap,而FastThreadLocal则是访问InternalThreadLocalMap。
每一个FastThreadLocalThread作为访问线程本地变量的接口,基本都会被定义为static + final类型的。
private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
@Override
protected Map<Stack<?>, WeakOrderQueue> initialValue() {
return new WeakHashMap<Stack<?>, WeakOrderQueue>();
}
};
每个FastThreadLocal在初始化的时候,都会通过InternalThreadLocalMap分配一个递增的index。
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
关键就是在这个index,这个index是final的,而且FastThreadLocal是static的,结合前面看到InternalThreadLocalMap内部存储的数组,而且每个FastThreadLocal只能指向一个对象,可以知道,每个线程通过同一个FastThreadLocal存储的对象,其存储在自身的InternalThreadLocalMap的位置都是一样的。具体可以看到下面例子
public static void main(String[] args) {
final FastThreadLocal<Integer> fastThreadLocal = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal2 = new FastThreadLocal<Integer>();
new FastThreadLocalThread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
fastThreadLocal2.set(12);
fastThreadLocal.set(13);
try {
Field field = FastThreadLocalThread.class.getDeclaredField("threadLocalMap");
field.setAccessible(true);
InternalThreadLocalMap internalThreadLocalMap = (InternalThreadLocalMap) field.get(Thread.currentThread());
System.out.println(internalThreadLocalMap);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}).start();
}
debug的结果
可以看到fastThreadLocal2晚于fastThreadLocal创建,所以被InternalThreadLocalMap分配的index小于fastThreadLocal,所以fastThreadLocal2即使在线程中调用set方法早于fastThreadLocal,也只会放在index=3的位置,而fastThreadLocal的数据则放在index=2的位置。同时我们再来看下一个例子
public static void main(String[] args) {
final FastThreadLocal<Integer> fastThreadLocal1 = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal2 = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal3 = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal4 = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal5 = new FastThreadLocal<Integer>();
final FastThreadLocal<Integer> fastThreadLocal6 = new FastThreadLocal<Integer>();
new FastThreadLocalThread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
fastThreadLocal6.set(13);
try {
Field field = FastThreadLocalThread.class.getDeclaredField("threadLocalMap");
field.setAccessible(true);
InternalThreadLocalMap internalThreadLocalMap = (InternalThreadLocalMap) field.get(Thread.currentThread());
Field field2 = UnpaddedInternalThreadLocalMap.class.getDeclaredField("indexedVariables");
field2.setAccessible(true);
Object[] indexedVariables = (Object[]) field2.get(internalThreadLocalMap);
int count = 0;
for(Object obj : indexedVariables) {
System.out.println(count++ + " ->" + obj);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}).start();
new FastThreadLocalThread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
fastThreadLocal6.set(12);
try {
Field field = FastThreadLocalThread.class.getDeclaredField("threadLocalMap");
field.setAccessible(true);
InternalThreadLocalMap internalThreadLocalMap = (InternalThreadLocalMap) field.get(Thread.currentThread());
Field field2 = UnpaddedInternalThreadLocalMap.class.getDeclaredField("indexedVariables");
field2.setAccessible(true);
Object[] indexedVariables = (Object[]) field2.get(internalThreadLocalMap);
int count = 0;
for(Object obj : indexedVariables) {
System.out.println(count++ + " ->" + obj);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}).start();
}
输出结果
4 ->java.lang.Object@37348c65
7 ->13
5 ->java.lang.Object@37348c65
6 ->java.lang.Object@37348c65
8 ->java.lang.Object@37348c65
9 ->java.lang.Object@37348c65
10 ->java.lang.Object@37348c65
11 ->java.lang.Object@37348c65
12 ->java.lang.Object@37348c65
13 ->java.lang.Object@37348c65
14 ->java.lang.Object@37348c65
15 ->java.lang.Object@37348c65
16 ->java.lang.Object@37348c65
17 ->java.lang.Object@37348c65
18 ->java.lang.Object@37348c65
7 ->12
8 ->java.lang.Object@37348c65
9 ->java.lang.Object@37348c65
可以看到两个FastThreadLocalThread通过变量fastThreadLocal6存放的数据,都是存放在自身InternalThreadLocalMap的同一个位置,因为这个位置在FastThreadLocal创建时就被定义。总结起来就是: FastThreadLocal在创建之后,所有FastThreadLocalThread调用这个对象并且存入数据时,存在在自身的InternalThreadLocalMap的位置都一样。
同样的,其实我们也会发现因为本身是数组,所以也会带来一定的空间浪费,这明显是一种时间换空间的方法。所以在内部的InternalThreadLocalMap的数组中,会用一个空对象占位来尽可能的减小空间的使用。
5、InternalThreadLocalMap的数组扩容
FastThreadLocal存入数据时,如果发现InternalThreadLocalMap的空间不够,则会尝试扩容。
public boolean setIndexedVariable(int index, Object value) {
// 数据都是存储在indexedVariables 数组里面,所以是Object
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;
}
代码比较简单,其中位运算参的最终目的就是扩容一倍。
// 9 换算称2进制为1001
1001 |= 1001 >>> 1; // 结果为1101
1101 |= 1101 >>> 2; // 结果为10000
10000 |= 10000 >>> 4; // 结果为10001 = 17
6、伪共享
参考文章:Netty 之线程本地变量 FastThreadLocal
7、参考