ThreadLocal理解
ThreadLocal深入理解
ThreadLocal是线程局部变量,ThreadLocal会为每个使用该变量的线程提供独立的变量副本,这种变量在线程的生命周期内起作用
每个线程有一个自己的ThreadLocalMap,所以每个线程中ThreadLocal的读写都是隔离的,不会相互影响;
一个ThreadLocal中只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal
对于每一个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashcode不可变属性,对于基本数据类型,可以认为它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象
作用
-
保存线程的上下文信息,在任意需要的地方可以获取
-
每个线程有自己独立的变量副本,且其它线程不可访问,就不存在多线程的安全问题
ThreadLocal实现原理
ThreadLocal是一个泛型类,保证可以接受任何类型的对象
因为一个线程内可以存在多个ThreadLocal对象,所以ThreadLocal内部维护了一个ThreadLocalMap的静态内部类,ThreadLocal对象的get、set方法其实都是调用了ThreadLocalMap类对应的get,set方法;
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
内存泄漏问题
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap中使用的key为ThreadLocal的弱引用,弱引用的特点是:如果这个对象只存在弱引用,那么在下一次垃圾回收的时候会被清理掉;
这样就会导致ThreadLocalMap中使用这个ThreadLocal的key会被清理掉,但是value是强引用,不会被清理,此时就会出现key为null的value;
ThreadLocalMap实现中已经考虑这个问题,在调用get(),set(),remove()的时候会清理掉key为null的记录,如果说存在内存泄漏,那只有在出现了key为null的记录之后,没有手动调用remove方法,并且之后也没有调用get(),set(),remove()方法;
Entry中的key值ThreadLocal为什么不设置成强引用?
如果使用强引用,当原来的key对象失效之后,jvm不会回收map里面的threadLocal
一个线程对应一块工作空间,线程可以存储多个ThreadLocal,那么假设开启一万个线程,每个线程创建一万个ThreadLocal,也就是每个线程维护一万个ThreadLocal的内存空间,而当线程执行结束以后,如果这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出
为什么使用ThreadLocal作为key?
同一个线程中,ThreadLocalMap中的key都相同,所以ThreadLocalMap中存储的数量由ThreadLocal决定,Map中Entry的数量会变少,当Thread销毁后,ThreadLocalMap也会随之消失,减少内存的使用
ThreadLocalMap的getEntry函数的流程大概为
-
首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (table.length-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;
-
如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry。否则,如果key值为null,则擦除该位置的Entry,并继续向下一个位置查询。在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。 但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的getEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
即:
1.使用ThreadLocal,建议用static修饰 static ThreadLocal headerLocal = new ThreadLocal();
2.使用完ThreadLocal后,执行remove操作,避免出现内存泄漏情况