JAVA基础:ThreadLocal原理解析
ThreadLocal用途
当需要声明一个Singleton的对象,想在多个线程中使用这个声明,每个线程使用自己独立的对象时。或者需要在线程内存储一个全局变量,仅当前线程可以访问。
ThreadLocal原理
看似一个容器
最早看到ThreadLocal的用法时,感觉好像是myThreadLocal是一个容器,它存储了我们放进去的对象,可以取出也可以移除。直到看了源码后才发现事实并不是这样,myThreadLocal只是一个KEY,真正的容器另有其人。
public class Main {
public static void main(String[] args) {
ThreadLocal<MyObject> myThreadLocal = new ThreadLocal();
myThreadLocal.set(new MyObject(12));
System.out.println(myThreadLocal.get());
myThreadLocal.remove();
}
}
class MyObject{
private Integer myValue;
MyObject(int x){
this.myValue = x;
}
public Integer getMyValue() {
return myValue;
}
public void setMyValue(Integer myValue) {
this.myValue = myValue;
}
}
实际的容器
下面来看一些源码
//设置值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取值
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象(真正的容器)
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以myThreadLocal对象自己为key,来获取Entry,Entry中存储了值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从上面的代码可以看出,实际上我们在"往myThreadLocal里放值或取值"时,其实是以myThreadLocal为key,在当前线程中的ThreadLocalMap对象中进行get和set操作。所以真正的容器是ThreadLocalMap对象。
ThreadLocalMap
我们看Thread的代码时,了解到每个Thread都有一个属性,它存储了当前线程的ThreadLocalMap对象,ThreadLocalMap中存储了所有的本地线程的对象。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal类是ThreadLocal内的一个静态内部类,它是自己实现了一个hash map,和hash map的数据结构相似,但是我们注意到它的Entry是一个弱引用WeakReference。它实现了一个简易版的WeakHashMap。这里需要讲一下弱引用是什么,为什么使用弱引用。
弱引用
概念上大家应该都清楚,就是如果它引用的对象只有它一个人在引用,那么在下次垃圾回收触发时,将回收它引用的对象,它将指向null。也就是说,弱引用不会影响对象的GC回收。
考虑下面的场景:现在有一个容器,或者有一个缓存,我们在容器外将对象A的所有引用置为null,来表示这个对象用不到了,如果是强引用:那么我们还需要在容器中手动将该对象A的引用置为空,这样对象A才能被回收。
关键点是:“从容器中移除这个不用的对象”这个工作我们不想自己完成,我们希望的是:当除了这个容器引用对象A以外,没有其他引用的时候就可以回收这个对象了。
这种情况,我们就可以在容器中使用弱引用指向对象A即可。
这就是WeakHashMap的作用。
引用关系图
如图所示,ThreadLocalMap是一个数组散列,每一个槽引用了一个Entry对象,这个对象继承了WeakReference,是一个弱引用。它指向的ThreadLocal对象如果外部没有引用指向此对象时,这个myThreadLocal对象会被回收。但是这里引用的myObject是强引用,区别是就算已经没有其他引用指向myObject时,myObject也不会被回收。
所以使用ThreadLocal里的remove()方法不仅执行了Reference的clear()方法(将弱引用置为空)同时还将entry置为空,这样就不会再引用myObject了。
这样的目的是,如果ThreadLocal这个对象被回收后,那么这个原来指向ThreadLocal对象的Entry就会指向null。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
对象的生命周期
Thread退出时,ThreadLocalMap对象会被回收。但是在使用线程池时,线程不会退出,如果使用了ThreadLocal而没有执行ThreadLocal.remove()方法的话可能会导致内存泄漏。