0%

ThreadLocal源码分析

前言

ThreadLocal类的作用就是对于每个线程,保存一份类的实例。

简单的说实现的话,就是存一个map,key就是当前的线程,value是该变量。
但是实际上的实现还是和我最初想的不一样,这个还要更复杂点。

底层结构

ThreadLocal

ThreadLocal中有三个变量,其中只有一个是成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private final int threadLocalHashCode = nextHashCode();

//这个可以看做是ThreadLocal的hashcode(),但是是通过nextHashCode得到的。
private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode =
new AtomicInteger();

//这个是每次增加的数,为啥要设为这个奇怪的数呢,我百度了下
//0x61c88647可以使 hashcode 均匀的分布在大小为 2 的 N 次方的数组里
//叫做Fibonacci Hash,这是我们下面进行Entry分配的基础。
private static final int HASH_INCREMENT = 0x61c88647;

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

ThreadLocalMap

每个Entry的key存的是ThreadLocal而并不是线程。

和HashMap的分离链表法不同,ThreadLocalMap中处理冲突的方法是开放定址法。
不会产生链表,而是往下找。
这就要求我们散列分的比较均匀。这也是采用Fibonacci的原因。

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
//这个Map的Entry继承了WeakReferenece,也就是弱引用。
//如果这个对象只被弱引用引用,那么它只能活到下一次gc前。
//这个把key作为一个弱引用,就是为了在我们ThreadLocal对象在不被使用后
//能被GC自动清除,避免内存泄漏
//当我们发现key == null的时候,就可以进行清理value的工作。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {

Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//初始的默认大小,参照HashMap的话,这里也必须是2的整数次方。
private static final int INITIAL_CAPACITY = 16;
//底层数组
private Entry[] table;
private int size = 0;
//下一次要扩展的容量大小。
private int threshold;

//构造函数,key是ThreadLocal
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//这个可以看做是Hash函数。因为是通过的Fibonacci Hashing实现的。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//其他的方法我们下面结合ThreadLocal方法看。
}

其中这个map的实例并不是放在ThreadLocal中,而是在Thread中。

1
2
3
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}

虽然是在Thread类中,但是还是在ThreadLocal中进行操作的。
Thread类很少对这个类进行操作。

SuppliedThreadLocal

这个类的定义在ThreadLocal中,提供了ThreadLocal的初始值。
正常的我们ThreadLocal如果没有进行set就get的话会得到一个null。

1
2
3
4
5
6
7
8
9
10
11
12
13
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

private final Supplier<? extends T> supplier;

SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}

@Override
protected T initialValue() {
return supplier.get();
}
}

这个类重写了initialValue方法,返回我们指定的初始值。
如果我们想得到这个类的实例,ThreadLocal提供了一个静态方法

1
2
3
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}

这个是在1.8中新增的。

基本操作

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

方法定义在ThreadLocal中,给线程t创建一个ThreadLocalMap,key为自己。

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

返回线程t的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
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
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();
}
//ThreadLocalMap中的getEntry方法
//首先进行Hash找到entry,如果发现key和我们的不同,那就是发生了两种情况
//第一种是已经被GC清除了,或者产生了冲突,这时候我们调用getEntryAfterMiss方法。
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);
}
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)
return e;
//如果是被GC收集掉了,那么调用expungeStaleEntry方法把后面的重新Hash
if (k == null)
expungeStaleEntry(i);
else
//不然就是继续找下一个桶。
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

//做清除工作。
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;

//将后面的Entry重新Hash
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

总结

  • key为弱引用,避免内存泄漏
  • 采用开地址法,运用Fibonacci Hash是hashcode分配均匀。

参考

https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html

Welcome to my other publishing channels