ThreadLocal是什么?
ThreadLocal可以给每一个线程分配属于自己的本地变量,让每个线程绑定自己的值,线程之间不互相影响。
ThreadLocal的使用:
创建了ThreadLocal变量之后,可以使用get()和set()方法来获取默认值,或者更改当前线程所保存的副本的值。
举例:
public class ThreadLocalDemo {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args){
Thread t1 = new Thread(() -> {
System.out.println(threadLocal.get());
threadLocal.set(0);
System.out.println(threadLocal.get());
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
threadLocal.set(1);
System.out.println(threadLocal.get());
});
t1.start();
t2.start();
}
}
运行结果:
null
0
null
1
我们创建了两个线程,在t1中第一次调用threadLocal.get()得到默认值null,然后在t1把threadLocal设置为0,再次get,得到我们刚才设置的0。然后在第二个线程t2中,我们先调用t1.join(),保证在t2中get之前,t1中的threadLocal已经set为0。然后我们再调用t2中的threadLocal.get(),发现得到的还是默认值null,这就说明两个线程中threadLocal是相互独立的,t1之中的threadLocal被设置为1了,但是t2之中的threadLocal并没有受到影响。
ThreadLocal的实现原理:
ThreadLocal是不保存任何值的,真正的值都保存在当前线程
里,每一个线程
里都有一个类型为ThreadLocalMap
的变量threadLocals
。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap我们可以理解为一个hashmap,其中的key是ThreadLocal对象,value就是我们自己set的值。我们创建一个ThreadLocal对象,就相当于创建了一个key,然后调用ThreadLocal.get(),就相当于在当前线程
维护的hashmap之中,找到这个key对应的值。举个例子:
public class ThreadLocalDemo {
//创建两个ThreadLocal,也就是两个key
static ThreadLocal name = new ThreadLocal();
static ThreadLocal age = new ThreadLocal();
public static void main(String[] args){
Thread t1 = new Thread(() -> {
//t1之中维护了一个map,保存两个key-value对
name.set("Tom");
age.set(18);
System.out.println(name.get() + "的年龄为" + age.get());
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//t2之中维护了一个map,保存两个key-value对
name.set("Jerry");
age.set(20);
System.out.println(name.get() + "的年龄为" + age.get());
});
t1.start();
t2.start();
}
}
运行结果:
Tom的年龄为18
Jerry的年龄为20
每一个线程都维护一个map,我们创建了两个ThreadLocal变量,分别为name和age,当我们在线程t1之中调用name.set(“Tom”)的时候,就相当于把(name,Tom)这个键值对保存到了t1的map下。其他的set、get也都是如此。通过这个例子,就可以很好的理解ThreadLocal的实现原理。
ThreadLocal内存泄漏问题:
经过分析,我们可以知道map键值对是保存在线程中的,并且是一个强引用,也就是说这个map键值对的生命周期是与线程一致的。这样就会造成即使我们回收了ThreadLocall对象,但是map中保存的数据也不会被回收。这样就会造成内存泄漏。
解决方法:每次用完ThreadLocal后手动remove(),回收无用对象。