ThreadLocal可以保存一个对象,在多线程环境下,每个线程可以创建自己对象,对自己所获取的对象进行操作,而不会影响其他线程。ThreadLocal并不是让多个线程安全的使用同一个对象,而是让每个线程拥有自己的对象。
在ThreadLocal中主要有get()获取当前线程对象,set(T value)设置当前线程对象,initialValue()初始化当前线程对象。ThreadLocal初始化时可以重写intialValue进行初始化,否则默认变量副本为null。
private static ThreadLocal<Obejct> local = new ThreadLocal<Object>() {
@Override
Object initialValue() {
reture new Object();
}
};
看到这里你可能会说,那我直接为每一个线程创建一个object对象不就行了,干嘛用ThreadLocal呀?这是因为这个对象可能会在线程执行过程中多处被使用,我们不想每次都创建它,或者将它作为一个参数传来传去的,使用ThreadLocal之后,就可以方便的使用threadLocal.get()的方式进行获取。
ThreadLocal的原理是在每一个thread中都有一个ThreadLocalMap对象。当调用ThreadLocal的get方法时,会先获取当前线程的ThreadLocalMap对象,以当前ThreadLocal对象实例为Key获取存储在map中当前线程的value,如果map为空,则根据initialValue值,初始化ThreadLocalMap将key,value放进去。
set也是如此,先获取当前线程的ThreadLocalMap对象,map如果存在,则将key对应的新值存入,map不存在则创建map,存入key,value
ThreadLocalMap底层是以数组的形式来存储的Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
内存泄漏:
threadLocal中的对象是存储在当前Thread的ThreadLocalMap中,ThreadLocalMap的底层数据结构是数组,存储的元素是Entry,它继承了ThreadLocal,Entry构造方法中需要传入ThreadLocal对象引用和vaule,在Entry中threadLocal是一个弱引用,如果强引用threadLocal = new ThreadLocal()被置为null,即threadLocal=null,那么进行一次GC后原来threadLocal引用指向的对象会被回收掉,这样使用了该对象的entry中的ThreadLocal也变成了null,它所对应的ThreadLocalMap中的entry也不能被获取到了,就有可能出现内存泄漏;
//ThreadLocalMap中的获取Entry的方法,因为对应的ThreadLocal对象已被回收,所以对应entry再也不能被获取到
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
但是即使Entry中的threadLocal为指向threadLocal对象的强引用,当threadLocal被置为null后,不光ThreadLocalMap中的entry连threadLocal原本指向的对象都不会被回收掉了,但是外部已经没有任何指向原ThreadLocal对象的引用了,内存泄露可能会更严重。
出现这种情况的原因是threadLocalMap它的生命周期与当前thread相同,当线程存活,threadLocalMap就不会被回收,只要不显式的调用threadLocal.remove方法,threadLocalMap中的entry永远都存在,因此将key使用弱引用,是ThreadLocal应对有可能发生的内存泄露自己所作的优化。
当我们使用ThreadLocal的get set remove方法时都会在本Thread的ThreadLocalMap中删除一些key为null的节点,这也是ThreadLocal为了避免内存泄漏所作的努力。但最好的方式是,当我们不再使用一个threadLocal中存储的对象时,主动调用它的remove()方法,减少threadLocal内存泄漏的可能性