Poison

ThreadLocal

在 Thread 的源码中,可以看到以下声明:

1
2
3
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

可以看出,Thread 实例持有了对 ThreadLocal.ThreadLocalMap 实例的引用,同时,threadLocals 变量的可见性为包可见,ThreadLocalThread 均属于包 java.lang

再看看 ThreadLocal 的几个关键方法:

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
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}

/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

在 get 方法中,首先获取到当前线程 t,然后使用 getMap 方法获取到当前线程 t 中的 threadLocals 实例的引用,如果该 map 不为空,则直接从 map 中获取该 ThreadLocal 实例对应的值,如果 map 为空,则初始化该 map。

初始化 Thread 实例中 threadLocals 实例的代码如下:

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
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

注意,ThreadLocalMap 中的 key 为 ThreadLocal 实例。

我们再看看 ThreadLocal 中用到的核心数据结构 ThreadLocalMap,该类作为 ThreadLocal 的静态内部类定义,如果没有看过 HashMap 实现原理的建议先看看笔者写的 HashMap。回到 ThreadLocalMap,先看看部分代码:

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
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;

/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;

可以看出,ThreadLocalMap 由 Entry 数组组成,初始容量为 16,且注释明确要求容量必须为 2 的 n 次幂,原因为当容量为 2 的 n 次幂时,定位元素所在数组下标的计算可以由取余操作优化为与运算实现,效率更高,关于 2 的 n 次幂带来的弊端可以参考笔者之前写的 HashMap,此处不再赘述,仔细看 Entry 类的定义,其继承了 WeakReference 且将 Entry 的 key 作为弱引用,value 依然是强引用。

再看看 ThreadLocalMap 的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

可以看出定位 key 所在数组下标采用了与运算实现。其中,不得不提的是 firstKey.threadLocalHashCode,我们看一下 ThreadLocal 中关于 threadLocalHashCode 的相关代码:

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
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();

/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();

/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;

/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

从注释中我们知道 ThreadLocalMap 采用线性探测处理 hash 冲突,每一个 ThreadLocal 实例的 threadLocalHashCode 值相差 0x61c88647,这个值是如何取到的呢?

首先根据源码可以看出 threadLocalHashCode 字段定义的类型为 int,在 Java 中,基本类型为有符号,那么可以知道 threadLocalHashCode 可以使用的范围为 [Integer.MIN_VALUE, Integer.MAX_VALUE],这一段范围值为 2 ^ 32 = 4294967296,根据黄金比例可以计算出根据黄金分割点将这段范围进行分割后,切分成了长度不等的两段,其中较长的段的长度为:(2 ^ 32) / ((1 + sqrt(5)) / 2) = 2654435769,那么容易得到较短的段的长度为 4294967296 - 2654435769 = 1640531527,在 Java 中 Integer.MAX_VALUE 的十进制表示为 2147483647,较长段长度 2654435769 因为大于了 Integer.MAX_VALUE 无法用 int 直接表示,而较短的段长度为 1640531527,可以用 int 表示,我们看看较长段长度 2654435769 的二进制表示:

1
2
2654435769:
10011110 00110111 01111001 10111001

可以看出如果把 2654435769 用 int 表示,则转换为十进制为 -1640531527,除了最高位为符号位之外,后面的二进制表示竟然与较短段 1640531527 的二进制表示相同,这也是黄金分割点的神奇之处。将该十进制 1640531527 转换为十六进制表示则为 0x61c88647,与源码中的值一致,笔者认为这就是作者选择 0x61c88647 作为 HASH_INCREMENT 值的原因。

当然,也有另一种说法是说采用的是黄金分割点的另一种变体,参见:What is the meaning of 0x61C88647 constant in ThreadLocal.java

References

ThreadLocal
Golden ratio
Fibonacci Hashing
Why should Java ThreadLocal variables be static