ThreadLocal解读,内存泄露
什么是ThreadLocal
ThreadLocal就是一个java类,这个类的作用和线程局部变量有关。什么是线程局部变量呢?我们之前肯定知道方法局部变量,作用域是定义该变量的方法,局部变量的生命周期是从函数被调用的时刻算起到函数返回调用处的时刻结束。同样,我们“线程局部变量”也是如此,作用域是当前单个线程,在线程开始时分配,线程结束时回收。我们可以理解为每个线程都有一个私人保险柜,我们往这个私人保险柜中存取,不会影响其他线程的私人保险柜。ThreadLocal就是这个私人保险柜。
一、ThreadLocal应用
代码如下(示例):
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<User> users = new ThreadLocal<>();
Thread th1 = new Thread(){
@Override
public void run() {
User user = new User();
System.out.println(Thread.currentThread().getName() + " 的user对象:" + user);
users.set(user);
}
};
th1.start();
Thread th2 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = users.get();
System.out.println(Thread.currentThread().getName() + " 的user对象:" + user);
}
};
th2.start();
}
}
class User{
private String name = "张三";
private Integer age = 22;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public User() {}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果:
Thread-0 的user对象:User{name='张三', age=22}
Thread-1 的user对象:null
我们创建两个线程,向线程th1的ThreadLocal中存放了User{name=‘张三’, age=22}对象,线程th2没有存放对象(null),我们对th1的修改不会影响到th2中ThreadLocal保存的对象。
二、ThreadLocal源码解读
1.set/get方法
set and get方法(示例):
//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);
}
//get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
简单读完上面代码,我们可以发现调用ThreadLocal的set/get方法时,都会去操作ThreadLocalMap对象。这是ThreadLocal中的一个静态内部类,我可以理解为这是一个专门为线程准备的HashMap,这个map的key当前线程创建的一个ThreadLocal实例名。
2.ThreadLocalMap
为什么要介绍ThreadLocalMap类了,因为我们在使用ThreadLocal的时候,很容易发生内存泄露。这个问题我们可能从没有在意过,我在这里解释一下为什么会出现这种情况。
首先我们需要了解什么是内存泄露,官方给出的定义是:
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
我这用大白话解释就是,如果一个对象,在堆内存中已经没有引用指向该对象(该对象已经没用了),但是GC还没有办法对它进行回收,那么这个对象就一直占用内存空间,这就是内存泄露。
代码如下(示例):
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;
}
}
}
不难发现,ThreadLocalMap 中使⽤的 key为ThreadLocal 的弱引⽤(WeakReference),但是ThreadLocalMap 中使⽤的 value是强引用。当GC运行的时候,发现弱引用就回收,于是key就被回收了。ThreadLocalMap里对应的Entry的key会变成null。这时候尴尬出现了,ThreadLocalMap里对应的Entry的value则无法被访问到,value作为一个强引用垃圾回收不到也不能被访问,即造成了内存溢出。