Netty关于ThreadLocal的优化

http://blog.csdn.net/zero__007/article/details/78288448 中简单介绍了一下ThreadLocal,每个ThreadLocal实例都有一个唯一的threadLocalHashCode初始值,在ThreadLocalMap中设置或获取Entry时,会根据threadLocalHashCode&(len-1)的值去对应的槽中操作。

而ThreadLocal解决Hash 冲突使用线性探测的方法,当一个线程对应多个ThreadLocal实例的场景中,在命中的情况下基本上一次hash就可以找到位置,如果发生没有命中的情况,则会引发性能会急剧下降,本身是O(1),结果变成了O(n)当在读写操作频繁的场景,这点导致性能的后滞。

作为一个高并发框架,Netty对ThreadLocal作了一些优化,并提供一个性能更好的FastThreadLocal。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class FastThreadLocalThread extends Thread {

private InternalThreadLocalMap threadLocalMap;

public FastThreadLocalThread() { }

public FastThreadLocalThread(Runnable target) {
super(target);
}

public FastThreadLocalThread(ThreadGroup group, Runnable target) {
super(group, target);
}

public FastThreadLocalThread(String name) {
super(name);
}

public FastThreadLocalThread(ThreadGroup group, String name) {
super(group, name);
}

public FastThreadLocalThread(Runnable target, String name) {
super(target, name);
}

public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) {
super(group, target, name);
}

public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) {
super(group, target, name, stackSize);
}

/**
* Returns the internal data structure that keeps the thread-local variables bound to this thread.
* Note that this method is for internal use only, and thus is subject to change at any time.
*/
public final InternalThreadLocalMap threadLocalMap() {
return threadLocalMap;
}

/**
* Sets the internal data structure that keeps the thread-local variables bound to this thread.
* Note that this method is for internal use only, and thus is subject to change at any time.
*/
public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
this.threadLocalMap = threadLocalMap;
}
}

Netty专门提供一个FastThreadLocalThread,继承了JDK的Thread,内部也有个InternalThreadLocalMap实例变量,并暴露了这个变量的getter/setter方法。

JDK中的ThreadLocalMap中的数组存放的是Entrty,key为ThreadLocal,value为真正保存的变量,而InternalThreadLocalMap中的数组里面的数据就是真正保存的变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FastThreadLocal<V> {

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

private final int index;

public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}

//……
}

​ JDK的ThreadLocal中将threadLocalHashCode放在了ThreadLocal类中,然后再拿这个值通过计算去定位ThreadLocalMap中的槽,而Netty的FastThreadLocal直接直接保存index,该index是通过InternalThreadLocalMap的静态方法获取的,该值是一个递增的值,也就是说不同的线程,在同一个FastThreadLocal中保存的变量值,在其实例变量InternalThreadLocalMap中的槽都是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {
static InternalThreadLocal<String> threadLocal = new InternalThreadLocal<String>(){
@Override
protected String initialValue() throws Exception {
return "zero";
}
};

public static void main(String[] args) {
InternalThread thread0 = new InternalThread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
});
InternalThread thread1 = new InternalThread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
});
thread0.start();
thread1.start();
}
}

上面的例子,线程私有的String变量,在其实例变量threadLocalMap里的数组,存放的位置下标都是1。由于InternalThreadLocal的index会递增,因此InternalThreadLocal实例再多的话也只会扩容,而不会发生Hash冲突。

在提一下FastThreadLocal中variablesToRemoveIndex,该变量是static final修饰的,会占用一个下标,即在InternalThreadLocalMap中会占一个槽。实际上该下标的元素是一个包装了IndentityHashMap的Set,每次FastThreadLocal中设置值的时候将自己加到该Set,移除值的时候将自己从该Set移除。也就是说初始值、设置值、删除值这几个功能的耗时比有值时的get更多。

性能比较看http://www.bubuko.com/infodetail-1932175.html