1.What is ThreadLocal?
ThreadLocal,可以理解为线程本地变量。它为T类型的变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,线程之间读写对象的操作是相互隔离、互不影响的,也就是不同线程的ThreadLocal之间是不可共享的。
/**下面是调用ThreadLocal的set方法,我们可以清晰的看到每次都会获取当前线程对象*/
public void set(T var1) {
Thread var2 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if (var3 != null) {
var3.set(this, var1);
} else {
this.createMap(var2, var1);
}
}
/**这个方法就是上面的this.getMap(),从中我们可以知道获取的其实是是保存在Thread里面的ThreadLocalMap,这个对象其实就是Thread里面的保存副本*/
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}
2.ThreadLocal辩析
先看一个定义:ThreadLocal类中,有一个静态内部类——ThreadLocalMap,虽然名字有Map,但并没有实现java的Map接口,其内部自定义的存储实体Entry类,继承了弱引用类WeakReference,以ThreadLocal的弱引用对象作为Entry的key,实际要存储的T类型对象,作为value。
Thread类中有一个类型为ThreadLocal.ThreadLocalMap的成员变量,名字叫threadLocals,默认为null,在进行get之前,必须先set,否则会报空指针异常。
如果不想set,则须重写initialValue方法。
当我们在程序中new一个ThreadLocal的对象local:
// 创建一个ThreadLocal
ThreadLocal<Object> local = new ThreadLocal<Object>() {
@Override
protected Object initialValue() {
return new Object();
}
};
// ThreadLocalMap 源码
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
/**可以看到ThreadLocalMap 内部持有一个Entry[]数组,这个数组就是用来存放变量的*/
private ThreadLocal.ThreadLocalMap.Entry[] table;
private int size;
private int threshold;
/**中间省略了很多代码*/
}
//Entry 源码,
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
调用local.set方法设值或者在get方法中执行initialValue方法初始化值后,当前线程的threadLocals中会有一个Entry,key弱引用指向对象local,value是Object对象,内存中的结构如下图:
为什么这里要使用弱引用呢?
- 首先,我们要了解引用和内存泄漏之间的关系,引用(Ref)是保存在Stack里面,而它的实例(Obj)是保存在Heap里面的,而我们使用Obj实例的时候是需要通过Ref获取的,导致内存泄漏的原因是我们将Ref=null,那么Obj就会在某些时候被GC回收,这是最理想的状态
- 其次,我们知道一个实例对象可能会被多个对象引用持有,在上面的图中,Entry如果是强引用的话,那么我们即使将ThreadLocalRef = null,那么ThreadLocal实例对象一定会被Entry的引用持有,那么ThreadLocal实例对象一定不会释放,故而将Entry使用弱引用
- 并且执行get和set方法的时候会回收key==null的value,这个就是使用弱引用的好处。
- 想要解决内存泄漏那就需要我们使用完当前的Threadlocal后调用threadLocal.remove()方法
ThreadLocal存在线程不安全的情况
如果定义static的ThreadLocal,那么就会出现线程不安全的情况
// 如下,因为number被不同线程改变的话引用是一样的,那么就出现了线程不安全的情况了,解决的办法是去掉static或者初始化ThreadLocal新建一个对象
public static Number number = new Number(0);