以下源码是基于JDK11。

Get以及Set

一开始,一直在疑惑这个ThreadLocal到底是用来干嘛的,其实他可以用来存储一些变量,这些变量普通存储会由于被多线程访问或导致一些并发问题,用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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public T get() {
// 这一步,它首先获取到了当前的线程,也就是说哪一个线程去调用ThreadLocal,就获取到哪一个线程。
Thread t = Thread.currentThread();
// 这一步就是获取到线程对应的ThreadLocalMap,后文会有具体的结构
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
// 这里就是从ThreadLocalMap中获取到对应的元素
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = e.value;
return result;
}
}

// 如果之前的map为空,则调用该方法
return this.setInitialValue();
}

private T setInitialValue() {
// 这个初始化值为null
T value = this.initialValue();
Thread t = Thread.currentThread();
// 这里又重新判断了一次,应该是考虑到其他的线程可能在此之间已经创建了对应的map
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
map.set(this, value);
} else {
// 这里判断map为空,那么就创建新的map
this.createMap(t, value);
}

if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal)this);
}

return value;
}

void createMap(Thread t, T firstValue) {
// 这个threadLocals的类型是ThreadLocalMap
// 这个创建,是为当前的线程创建一个ThreadLocalMap
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}

public void set(T value) {
// 这一步set的设计思路与get一致,都是获取到当前的线程之后,再进行操作。
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
map.set(this, value);
} else {
this.createMap(t, value);
}
}

其实中源码中可以看出,get和set操作都是先获取到当前线程,而每一个Thread中有一个变量ThreadLocalMap,然后去该变量中获取对应的元素。也就是说在每一个Thread中存储了对应的变量,这样就避免了并发访问的问题。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap内部还有一个内部类,表示ThreadLocalMap的每一个节点。

首先看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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static class ThreadLocalMap {
// creatMap调用的构造方法就是该方法。
// 这里创建该ThreadLocalMap时,需要传入ThreadLocal,其目的就是为了获取对应的hash值,然后将该元素放入对应的下标即可。
// 而且要获取到该ThreadLocal的table,因为元素的值需要与ThreadLocal绑定,我们要能通过ThreadLocal来拿到对应的元素。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
int i = firstKey.threadLocalHashCode & 15;
this.table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
this.size = 1;
this.setThreshold(16);
}

private ThreadLocalMap(ThreadLocal.ThreadLocalMap parentMap) {
ThreadLocal.ThreadLocalMap.Entry[] parentTable = parentMap.table;
int len = parentTable.length;
this.setThreshold(len);
this.table = new ThreadLocal.ThreadLocalMap.Entry[len];
ThreadLocal.ThreadLocalMap.Entry[] var4 = parentTable;
int var5 = parentTable.length;

for(int var6 = 0; var6 < var5; ++var6) {
ThreadLocal.ThreadLocalMap.Entry e = var4[var6];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal)e.get();
if (key != null) {
Object value = key.childValue(e.value);
ThreadLocal.ThreadLocalMap.Entry c = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int h;
for(h = key.threadLocalHashCode & len - 1; this.table[h] != null; h = nextIndex(h, len)) {}
this.table[h] = c;
++this.size;
}
}
}
}

// ThreadLocalMap的一个静态内部类
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

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

这里的Entry,就是对应存储的具体值,比如说如下代码

1
2
// 该代码中,Entry就是一个City
ThreadLocal<City> threadLocal = new ThreadLocal<>();

每一个Thread有一个变量ThreadLocalMap,在创建该变量时,需要传入首个key以及value,其中key是一个ThreadLocal。

为什么要传入这个ThreadLocal呢?

我的理解是ThreadLocal要与对应的Thread绑定。换句话说,我们在使用ThreadLocal时,一般都是在多个线程中通过ThreadLocal来获取一个本该是共享的变量,而在ThreadLocal内部,它会先获取到当前线程,然后调用该线程的ThreadLocalMap来返回线程所需要的变量。所以,创建ThreadLocalMap时就需要传入一个ThreadLocal,方便后续获取值。

Get以及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
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* 这里,是根据传入的key先获取到hash值,然后去table的对应下标获取元素,存在则直接返回,不存在则调用getEntryAfterMiss
*/
private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & this.table.length - 1;
ThreadLocal.ThreadLocalMap.Entry e = this.table[i];
return e != null && e.get() == key ? e : this.getEntryAfterMiss(key, i, e);
}

/**
* 这里就是遍历table所有的元素(该ThreadLocalMap存储的所有元素),找是否有key匹配的,有就直接返回
* 如果table中某一个值为空,则调用expungeStaleEntry方法
*/
private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocal.ThreadLocalMap.Entry e) {
ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;

for(int len = tab.length; e != null; e = tab[i]) {
ThreadLocal<?> k = (ThreadLocal)e.get();
if (k == key) {
return e;
}

if (k == null) {
this.expungeStaleEntry(i);
} else {
i = nextIndex(i, len);
}
}

return null;
}

// 该方法的主要目的是为了清除对应下标的元素
private int expungeStaleEntry(int staleSlot) {
ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
int len = tab.length;
tab[staleSlot].value = null;
tab[staleSlot] = null;
--this.size;

ThreadLocal.ThreadLocalMap.Entry e;
int i;
// 这里是遍历该ThreadLocalMap的Entry数组,也就是看该ThreadLocalMap存储的所有元素
for(i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
// 获取到对应的ThreadLocal
ThreadLocal<?> k = (ThreadLocal)e.get();
// 这里应该是考虑对应的ThreadLocal已经被释放了
if (k == null) {
e.value = null;
tab[i] = null;
--this.size;
} else {
// 这几步操作,就是为了调整元素位置,将他们放在合适的位置,以减小tab大小
int h = k.threadLocalHashCode & len - 1;
if (h != i) {
// 这一步是循环,断开强引用
for(tab[i] = null; tab[h] != null; h = nextIndex(h, len)) {
}

tab[h] = e;
}
}
}

return i;
}

private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
int len = tab.length;
int i = key.threadLocalHashCode & len - 1;

for(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = (ThreadLocal)e.get();
if (k == key) {
e.value = value;
return;
}

if (k == null) {
this.replaceStaleEntry(key, value, i);
return;
}
}

tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++this.size;
if (!this.cleanSomeSlots(i, sz) && sz >= this.threshold) {
this.rehash();
}

}

private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
int len = tab.length;
int slotToExpunge = staleSlot;

ThreadLocal.ThreadLocalMap.Entry e;
int i;
for(i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) {
if (e.get() == null) {
slotToExpunge = i;
}
}

for(i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = (ThreadLocal)e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot) {
slotToExpunge = i;
}

this.cleanSomeSlots(this.expungeStaleEntry(slotToExpunge), len);
return;
}

if (k == null && slotToExpunge == staleSlot) {
slotToExpunge = i;
}
}

tab[staleSlot].value = null;
tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
if (slotToExpunge != staleSlot) {
this.cleanSomeSlots(this.expungeStaleEntry(slotToExpunge), len);
}

}

这里,ThreadLocalMap是用Entry[]去保存value,而Entry继承了WeakReference,查资料得知这里Entry的key是弱引用,意味着当ThreadLocal没有外部强引用时,会被GC回收,而对应的该ThreadLocal对应的value却不会被回收,若当前线程一直没有结束,会存在一条强引用链, value也会一直累加导致内存泄露。

expungeStaleEntry方法就是为了解决上述问题。主动去清理key为空的ThreadLocal对应的value。