Java高并发编程之ThreadLocal
ThreadLocal 在java中是充当“线程本地变量”使用,通过set()和get()来对这个局部变量进行操作,在每个线程都会创建该对象从而实现线程之间的隔离,具有一定程度上的线程安全。ThreadLocal常见应用场景为数据库连接、Session管理等。
# 深入ThreadLocal类
ThreadLoca提供一下常用方法:
protected T initialValue(); // 用来在使用时进行重写的,是一个延迟加载方法
public T get(); // 获取ThreadLocal在当前线程中保存的变量副本
public void set(T value); // 设置当前线程中变量的副本
public void remove() // 移除当前线程中变量的副本
2
3
4
# get()方法实现
public T get() {
Thread t = Thread.currentThread(); // 取得当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程对应的ThreadLocalMap
if (map != null) { // Not Null
ThreadLocalMap.Entry e = map.getEntry(this); // 获取到<key,value>键值对,注意这里获取
// 键值对传进去的是this,而不是当前线程t。
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result; // 存在,则返回value值。
}
}
return setInitialValue(); // 否则调用setInitialValue方法返回value
}
2
3
4
5
6
7
8
9
10
11
12
13
14
ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为Key。 setInitialValue()实现:
private T setInitialValue() {
T value = initialValue(); // return null;
Thread t = Thread.currentThread(); // 取得当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程对应的ThreadLocalMap
if (map != null)
map.set(this, value); // 值为null,只有调用set()方法时才初始化value
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); // 创建当前线程对应的ThreadLocalMap
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# set()方法实现
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的成员变量map
if (map != null)
map.set(this, value); // map非空,则重新将ThreadLocal和新的value副本放入到map中。
else
createMap(t, value); // map空,则对线程的成员变量ThreadLocalMap进行初始化创建,
// 并将ThreadLocal和value副本放入map中。
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# remove()方法实现
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2
3
4
5
6
7
8
9
小结:在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,key为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为key,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
在进行get之前,必须先set,否则会报空指针异常。如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
# 内存泄漏问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
解决方案: 既然Key是弱引用,解决方案就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。