ThreadLocal源码分析

因为资源位重构项目中使用了ThreadLocal存放用户权限数据,作为访问接口敲门砖的同时也为接口业务分析提供了用户数据,因此去研究了一下ThreadLocal源码,源码涉及 ThreadThreadLocalInheritableThreadLocal 三个类文件。

定义

ThreadLocal 作为一个泛型类,主要作用是提供线程局部变量,这些变量和普通变量不同之处在于访问它们的每个线程都有属于它们自己的、独立初始化的变量副本,不同线程之间数据独立不共享。

继承了 ThreadLocalInheritableThreadLocal 提供的线程局部变量可在父子线程间进行传递。

ThreadLocal 变量通常被private static修饰,当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收,但是对应的value却还存在与内存中,因此可能造成内存泄漏,需要在线程结束之前remove掉数据。

源码

Thread、ThreadLocal、InheritableThreadLocal类别关系

Thread类里面维护了两个ThreadLocalMap对象

  1. threadLocals 存放了只能当前线程获取的局部变量
  2. inheritableThreadLocals 存放可被子线程继承同时继承父线程的局部变量 inheritableThreadLocals

每个ThreadLocalMap对象维护了一个Entry[] table数组,存放了所有的线程局部变量,同时维护了每个ThreadLocal与线程局部变量的映射关系,我们可以通过其中一个ThreadLocal实例获取此Thread实例在此ThreadLocal中的变量值。

线程与ThreadLocal的关系图如下:

源码分析

在Thread类中,维护两个ThreadLocalMap对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Thread implements Runnable {
......
/*
* 当前线程的ThreadLocalMap,主要存储该线程自身的Entry<ThreadLocal,value>[]数组
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
* 主要用于父子线程间ThreadLocal变量的传递
* Entry<InheritableThreadLocal,value>[]数组
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......
}

threadLocals 的数据来源有两种:

  1. ThreadLocal.set() 方法的调用;
  2. 重写 ThreadLocalinitialValue() 方法,ThreadLocal.get() 方法调用且 threadLocal 无局部变量时触发

inheritableThreadLocals 的数据来源有三种:

  1. 子线程创建的时候从父线程中继承线程局部变量
  2. InheritableThreadLocal.set() 方法的调用;
  3. 重写 inheritableThreadLocalinitialValue() 方法,inheritableThreadLocal.get() 方法调用且 inheritableThreadLocal 无局部变量时触发的 initialValue() 方法.

在ThreadLocal类中,我们经常用到的几个方法是get()、set()和remove()

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
//获取当前线程中的局部变量
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程中维护的threadLocals对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取当前线程在此ThreadLocal实例中的局部变量
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(unchecked)
T result = (T)e.value;
return result;
}
}
//若在get()前没有set()当前线程的数据,则初始化数据,可根据需要重写initialValue()方法
return setInitialValue();
}


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



//往当前线程的ThreadLocalMap里面塞值
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程的threadLocals对象
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//当前线程threadLocals为null时,初始化当前线程的threadLocals字段并将<ThreadLocal,value>放入threadLocals中
createMap(t, value);
}



//在当前线程的threadLocals对象中删除该ThreadLocal实例
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}


//移除key的弱引用&清除数据&rehash table[]
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//计算key所在的初始下标
int i = key.threadLocalHashCode & (len-1);
//从数组tab下标为i循环往后遍历,直到遍历循环找到当前的ThreadLocal的弱引用或者e为null,
//ThreadLocalMap对象的size永远小于tab.len,即便key不存在tab中,也会因为e==null而结束遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
//expungeStaleEntry(i)用于清除e对应的value,防止内存泄漏;
//同时rehash tab下标为i后面entry,解决因删除数据发生的entry冲突
expungeStaleEntry(i);
return;
}
}
}

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

在InheritableThreadLocal类中,我们经常用到的几个方法是set()、get()、remove()

Thread在创建线程时的 init() 方法中继承了父线程的 inheritableThreadLocals 数据

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
//Thread类中的初始化线程数据的方法,子线程继承了父线程的inheritableThreadLocals数据
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}


//拷贝父线程可继承的ThreadLocal数据
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}


//拷贝父线程的数据
private ThreadLocalMap(ThreadLocalMap parentMap) {
//获取父线程的inheritableThreadLocals数据
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings(unchecked)
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//InheritableThreadLocal重写了childValue方法,ThreadLocal不能调用该方法
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}


//InheritableThreadLocal重写了ThreadLocal方法
protected T childValue(T parentValue) {
return parentValue;
}


//InheritableThreadLocal重写了ThreadLocal中获取和设置线程ThreadLocalMap的数据,其他get()、set()并无二致
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}


void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

应用场景

ThreadLocal 的目的是解决多线程场景中出现相同变量访问冲突问题,当某些数据是以线程为作用域并且不同线程具有不同数据的时候,就可以考虑采用 ThreadLocal 。比如用户访问接口时需要用户数据,每个用户的数据都不同,但是局部变量相同,因此就可以使用 ThreadLocal 存储数据

注意场景

ThreadLocal 的错误使用可能会出现以下问题:

  1. 防止内存泄漏

    ThreadLocalMap 类维护了 ThreadLocal 与具体实例的映射, ThreadLocalMap 的每个 Entry 都是一个对键的弱引用,而Entry却包含了对值的强引用。使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,但是当  ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry无法被移除。从而使得实例被该 Entry引用而无法被回收造成内存泄漏。因此在不用线程局部变量或者线程结束之前,先将数据remove掉。

  2. 脏数据

    线程池的存在会导致线程复用场景出现,若上一个线程没有在使用完成之后清除 ThreadLocal 信息或下一个线程在使用前没有清理数据、 ThreadLocal 初始化,都可能会拿到上个线程设置的错误数据。