程序中需要全局变量,想要直接用一个全局的变量,但涉及到多线程的问题,改用ThreadLocal ,codeReview 时,被指出没有显示的调用 ThreadLocal 的remove 方法,可能会造成内存泄漏。但是对ThreadLocal 为什么会造成内存泄漏并不了解。之前一个项目重构时,重构前ThreadLocal的使用,并没有花时间去了解,这里记录下自己通过资料对 ThreadLocal 的理解。
什么是ThreadLocal
ThreadLocal 类可以提供线程内变量,即多线程中每个线程中该变量都是相互隔离的,一个线程对自己线程中变量的修改,不会影响到其它线程。
使用
get
获取当前线程对应变量的值
set
设置当前线程对应变量的值
remove
删除 ThreadLocal 中该线程的绑定的值
如何实现线程隔离
ThreadLocal 如何实现线程中的变量相互隔离的呢?
- 比较简单的方法就是在ThreadLocal中设置一个map,每个线程作为key,保存各自的变量
- 另一种就是,每个Thread中保存一个map,每个ThreadLocal 对象作为key
JDK 早期版本是使用第一种方案,现在使用第二种方案。
源码解析
ThreadLocal 的源码(jdk1.7):
get 方法
public T get() {
Thread t = Thread.currentThread();// 1, 拿到当前线程
ThreadLocalMap map = getMap(t); // 2, 拿到当前线程中的 threadLocalMap
if (map != null) { // 3, 若map不为空,将map 中key 为当前Thread实例的value取出,并返回
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue(); // 4, 如果map 为空,会将value设置初始值,并放入map中,最终返回初始值
}
getMap 源码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 是Thread 类中的一个ThreadLocal.ThreadLocalMap 类型的变量
}
Thread 类中的 threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这里可以看出,Map类型虽然是 ThreadLocal 中的ThreadLocalMap, 但是该变量是保存在 Thread中的。
setInitialValue 源码:
private T setInitialValue() {
T value = initialValue(); // 1, 初始化 value
Thread t = Thread.currentThread(); // 2, 获取当前 thread
ThreadLocalMap map = getMap(t); //3, 依旧去拿thread 中的map
if (map != null) //4, map 存在,将初始值放入map中
map.set(this, value);
else
createMap(t, value); //5,map不存在,创建并设置key value
return value; //6, 返回value
}
其中initialValue(可以重写该方法) :
protected T initialValue() {
return null;
}
set 方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
过程与setInitialValue 方法类似。
remove 方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); // 将 Thread 中 ThreadLocalMap 中该 ThreadLocal 对象的entry 移除
}
何时会发生内存泄漏
为什么需要显示的将一个线程中 ThreadLocalMap 中该 ThreadLocal entry 删除呢
ThreadLocalMap 中的Entry 对 Key使用的是WeakReference,ThreadLocalMap 在getEntry 以及 set 方法中,发现key 为null 的entry 会将其删除掉。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) { // 这里 Entry 中并没有保存key,而是将其 与ReferenceQueue 关联,弱引用一但GC 就会被回收
super(k);
value = v;
}
}
.....
}
当没有对ThreadLocal 实例的强引用后,它后被回收掉,此时ThreadLocalMap 就存在了 key 为null 的entry。但是该map 只有在调用 set 或者 getEntry 方法时,才会移除这种 entry。 在使用了线程池的情况下,线程不会结束,此时 Thread -> ThreadLocalMap -> Entry -> value -> Object value 的值一直会被强引用,不会被回收,造成内存泄漏。
http://blog.csdn.net/imzoer/article/details/7996400
https://juejin.im/entry/5662895900b0bf3758a69736
http://www.cnblogs.com/onlywujun/p/3524675.html