源码分析

ThreadLocalMap

学习ThreadLocal之前有必要先学习一下它的内部类ThreadLocalMap

1. 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* The initial capacity -- MUST be a power of tw
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0

HashMap不同的是,TheadLocalMap 没有负载因子这个变量,但实际上负载因子是 2/3,且不可变

1
2
3
4
5
6
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

2. Entry

1
2
3
4
5
6
7
8
9
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

与我们最常用的 HashMap 类似的是,ThreadLocalMap 也有一个 Entry ,但与之不同的是这个 Entry 类继承了 WeakReference

从该类的结构,我们可以看出这个 Map 以 TheadLocal 对象作为 key,并且这个 key 是弱引用。这样若ThreadLocal对象在其它地方没有强引用,在 JVM执行GC的时候,即使 Entry 持有这个 ThreadLocal 对象,也会被回收掉,防止出现 TheadLocal 对象不再使用却无法回收的情况,以防内存泄漏。

虽然 TheadLocal 对象被标以弱引用,但Entry对象的value却依旧是强引用,因此在TheadLocal 在执行获取、插入、删除等操作的时候会执行以下代码解除对value的引用,

1
2
3
4
5
if (k == null) {
e.value = null;
tab[i] = null;
size--;
}

值得注意的是 Entry 类并没有next指针,因此 TheadLocalMap 主要的数据结构仅仅只是 Entry 数组,这么做主要是方便不需要的Entry被回收。

3. Expunge

3.1 expungeStaleEntry

该方法主要的作用是解除无效 key 的Entry对象,并重新调整存在Hash冲突的 Entry 对象的位置

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
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
// 直接解除对 Entry 对象的引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
// 清除无效 Entry,并调整重新调整 Hash 冲突,直到遇到 null 节点
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
// 清除无效Entry
e.value = null;
tab[i] = null;
size--;
} else {
// Key 值的 hash 码
int h = k.threadLocalHashCode & (len - 1);
// hash码与下标不一致,说明存在冲突,重新调整
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

3.2 cleanSomeSlots

顾名思义,清除一些节点,因此不用全部扫描,扫描的次数由n控制,发现无用节点时,调用expungeStaleEntry 清理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private boolean cleanSomeSlots(int i, int n) {

boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}

3.3expungeStaleEntries

扫描整个 Entry 数组

1
2
3
4
5
6
7
8
9
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}

4. set

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
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

// 若没有hash冲突,则直接插入
// 若存在hash冲突,则调用 nextIndex 遍历后面的节点,直到找到可插入的节点
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// key 值相等,直接替换
if (k == key) {
e.value = value;
return;
}
// key值无效,则替换
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;

// 试图清除一些节点
// 若没有需要清除的节点,且数据长度达到了 theshold,则rehash对entry数组进行调整
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

5. getEntry

getEntry 通过 hash 值获取到有效值,则直接返回,若未命中key值,则调用 getEntryAfterMiss方法获取value

1
2
3
4
5
6
7
8
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);
}

getEntryAfterMiss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
// 命中key,返回
return e;
if (k == null)
// 无效节点,清除
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
// 至此,未命中 返回null
return null;
}

ThreadLocal 源码

变量

1
2
3
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
1. threadLocalHashCode

Thread 类中维护了一个 ThreadLocalMap,在这个Map中,ThreadLocal将作为Key值传入,因此 threadLocalHashCode 就会作为 ThreadLocal 对象在每个线程的 threadLocalMap 中的散列值。

2. nextHashCode

给出 ThreadLocal 对象的 theadLocalHashCode 值。

1
2
3
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

get

ThreadLocal 相较于它的内部类 TheadLocalMap,代码更加精简

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
// 没有获取到value,则插入默认值并返回
return setInitialValue();
}

set

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);// 懒加载
}