ThreadLocal源码分析

​ ThreadLocal这个类给线程提供了一个本地变量,这个变量是该线程自己拥有的。在该线程存活和ThreadLocal实例能访问的时候,保存了对这个变量副本的引用.当线程消失的时候,所有的本地实例都会被GC。建议ThreadLocal最好是 private static 修饰的成员。

1
2
3
4
5
6
7
8
9
10
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

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

​ ThreadLocals 解决Hash 冲突使用线性探测的方法。其中key为ThreadLocal对象,因为Thread类中的threadLocals是个map对象,意味一个Thread中可以存放多个ThreadLocal对象。因此用threadLocalHashCode来区分是哪个ThreadLocal类型。

get方法:

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;
}
}
return setInitialValue();
}

​ 返回当前线程的threadLocal变量,如果这个变量对当前线程没有值,则它调用前面介绍的initialValue方法获取初始值。

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

​ Thread中threadLocals定义:

1
ThreadLocal.ThreadLocalMap threadLocals = null;

​ 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
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0

private void setThreshold(int len) {
threshold = len * 2 / 3;
}

private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

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

​ 内部是Entry数组,Entry是继承WeakReference。ThreadLocalMap的getEntry方法:

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

​ 先结合threadLocalHashCode与table.length – 1的值确定该ThreadLocal在ThreadLocalMap的Entry数组下标位置。然后根据下标如果能在table找到直接返回,否则调用getEntryAfterMiss:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

​ 解释下e.get() == null表示这个key引用被垃圾回收了,因此需要调用expungeStaleEntry方法从数组里删除该Entry;如果其它情况需要向后找下一个Entry,因为采用的是线性探测法解决冲突的。

set方法:

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

​ ThreadLocalMap的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
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

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

if (k == key) {
e.value = value;
return;
}

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

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

​ for循环中是根据计算得出的下标在table中找Entry,当Entry不为空时,如果是同一个对象,k == key,直接替换Entry里面的value值;如果k为null,说明k被回收了,重新放入该位置;否则向后继续找Entry,直到Entry为空。

​ 如果计算后的坐标获取到的entry为null,就new一个Entry对象并保存进去,然后调用cleanSomeSlots()对table进行清理,如果没有任何Entry被清理,并且数组的size超过了阈值,就会调用rehash()方法。

​ cleanSomeSlots()中会调用expungeStaleEntry清理不用的Entry。rehash则会调用expungeStaleEntries()方法清理所有不用的Entry:

1
2
3
4
5
6
7
8
private void rehash() {
expungeStaleEntries();

// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}