Netty中FastThreadLocal源码解析

简介

ThreadLocal一个特殊变体,当从FastThreadLocalThread访问时,可获得更高的访问性能。
在内部, FastThreadLocal在数组中使用常量索引来查找变量,而不是使用哈希码和哈希表。 尽管看似非常微妙,但与使用哈希表相比,它在性能上却有一点优势,并且在经常访问时很有用。
要利用此线程局部变量,您的线程必须是FastThreadLocalThread或其子类型。 由于这个原因,默认情况下, DefaultThreadFactory创建的所有线程均为FastThreadLocalThread
请注意,只有在扩展FastThreadLocalThread线程上才可以使用快速路径,因为它需要一个特殊的字段来存储必要的状态。 任何其他类型的线程的访问都回退到常规ThreadLocal

上面这段描述来自FastThreadLocal源码中的文档,从中可以知道FastThreadLocal必须和FastThreadLocalThread或其子类型一起使用才可以达到Fast的效果。

FastThreadLocalThread

既然必须要和FastThreadLocalThread 一起使用,那就来看看FastThreadLocalThread到底有什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* A special {@link Thread} that provides fast access to {@link FastThreadLocal} variables.
* 一种特殊的{@link Thread},可以快速访问{@link FastThreadLocal}变量。
*/
public class FastThreadLocalThread extends Thread {


// This will be set to true if we have a chance to wrap the Runnable.
private final boolean cleanupFastThreadLocals;

private InternalThreadLocalMap threadLocalMap;

public FastThreadLocalThread() {
cleanupFastThreadLocals = false;
}

public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
……

省略了一部分代码,可以看到FastThreadLocalThread继承自Thread,额外多了一个InternalThreadLocalMap threadLocalMap,从doc中可以看出这个变量就是关键。

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s.
* Note that this class is for internal use only and is subject to change at any time. Use {@link FastThreadLocal}
* unless you know what you are doing.
*/
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {

static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();

static final AtomicInteger nextIndex = new AtomicInteger();

/** Used by {@link FastThreadLocal} */
Object[] indexedVariables;

public static final Object UNSET = new Object();

public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}

private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}

private static InternalThreadLocalMap slowGet() {
ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}

public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}

/**
* @return {@code true} if and only if a new thread-local variable has been created
*/
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;
}
}


InternalThreadLocalMap继承自UnpaddedInternalThreadLocalMap,为了好看一点我把父类中的相关代码放到了一起,dubbo借鉴netty代码也是这么干的。

FastThreadLocal

最后看看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
public class FastThreadLocal<V> {

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

private final int index;

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

/**
* Returns the current value for the current thread
*/
@SuppressWarnings("unchecked")
public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}

return initialize(threadLocalMap);
}

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;
}

/**
* Returns the initial value for this thread-local variable.
* 这个方法一般会把覆盖
*/
protected V initialValue() throws Exception {
return null;
}

1

八卦

https://github.com/apache/dubbo/pull/1745

作者

太阳当空赵先生

发布于

2021-05-08

更新于

2022-06-29

许可协议

评论