ThreadLocal 百度上面对于他的解释已经非常非常多了,但是很多其实都是复制来复制去,并且很多都是说的错的,最离谱的是百度上面头几页说ThreadLocal在Set的时候内部的ThreadLocalMap存放的Key是当前线程对象...,真的简直在虾扯蛋,不知道误导了多少人
ThreadLocal的API非常简单,这里就不在描述了,主要针对他源码以及设计思路来分析设计者的用意,要了解ThreadLocal原理那么先要了解Thread和ThreadLocalMap是啥...
Thread是Java种的线程类,其中有一段这样的代码,从这段代码的注释种可以看出来这个属性是给ThreadLocal 来使用的,那么具体如何使用的下文会慢慢解析
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是ThreadLocal中的内部类,它类似我们所知的常规HashMap,但是仅仅是数据结构类似里面的实现和常规HashMap完全不一样
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
从注释和源码可以发现,这个ThreadLocalMap的Entry继承弱引用(软引用、弱引用、虚引用这个可以自行百度,不在这里展开了),ThreadLocalMap的key存放的是ThreadLocal对象,而这个ThreadLocal对象是弱引用,为什么要这么做,下文也会慢慢解释
这个其实利用Java的栈来做到的,当变量是局部变量时,这个变量的申请空间就会在当前线程的执行栈中,其他线程是无法访问这个变量的,这个就用到了上面的Thread类中的属性,每一个Thread内部都维护了一个自己的threadLocals变量,而这个threadLocals变量的赋值通过了ThreadLocal的内部类threadLocalsMap
public void func1() {
ThreadLocal tl = new ThreadLocal(); //line1
tl.set(100); //line2
tl.get(); //line3
}
line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:
当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且在entry的k引用为null后,再调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。
概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。
注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现k为null时才会去回收整个entry、value,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到ThreadLocal在set的时候,map的key是当前ThreadLocal对象,并且这个对象是一个弱引用,当同一个线程内部使用了多个ThreadLocal时,如果发生了Hash冲突,ThreadLocalMap会直接利用Entry下标+1的方式往后走,直到Hash冲突消失(这种方式称为:拉链法)