不就是ThreadLocal
最近在重构代码时,发现有不同类之间参数的传递很复杂,想到了之前看到的ThreadLocal,于是就想使用ThreadLocal来解决参数传递的问题,但是在使用之前还是先看了下ThreadLocal的源码,避免后面出现问题。
先简单说下ThreadLocal的实现原理,然后再跟着源码看下。
每个ThreadLocal实例对应一个当前运行的Thread线程,每个Thread线程又有一个ThreadLocalMap,通过ThreadLocal类的set方法将需要使用的参数保存在ThreadLocalMap这个map中,后面只要在同一个线程中利用ThreadLocal实例的get方法就可以得到之前设置的参数。
其实,在看源码之前,可以简单思考下实现的套路无非就是:
1. 获取当前线程。
2. 获取此线程的ThreadLocalMap
3. 根据这个map的key获取value。
那么,问题来了,这个key是啥呢,那就开始看下源码吧。
首先看下ThreaLocal的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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
其实,从源码来看,实现的套路和我们之前猜想的是一样的,主要是看下这个map的key是啥,从map.set(this, value)这句可以看到,这个map的key就是ThreadLocal实例的引用。
然后,看下ThreaLocal的get方法。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get方法的过程也很简单,根据threadLocal实例的引用去获取ThreadLocalMap中Entry对象,然后获取value值。
上面的源码很简单,主要的问题是ThreadLocal可能存在内存泄露,这也是使用之前想看下源码的原因,下面就看下为啥会出现内存泄露。
内存泄露一定是存储的数据没有及时释放,导致数据占用的内存越来越大,从上面的源码来看,数据是存储在哪里的呢,很明显是在ThreadLocalMap中,那放在map中的数据为啥占用的内存越来越大呢,那就要看下这个map的源码有啥特点了。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
从上面的源码可以发现,Entry对象继承了WeakReference类,这个类的作用主要就是使某个对象的引用为弱引用,那么弱引用有啥特点呢,简单来说,只要触发GC,不管这个实例有没有被其他对象引用,都会被回收。
写到这里,又想到前段时间利用WeakHashMap来简单实现缓存功能,其实也是利用了WeakHashMap弱引用的特点,避免缓存越来越大,导致内存溢出。
继续刚才的分析,可能不太熟悉的同学这里可能会有疑问,既然弱引用这么容易回收,那么更不可能出现内存泄露了。其实,只是map的key为弱引用,那么key回收后就变为了null,但是value还没有被回收呢。假如,我们使用的是线程池,由于线程池中的线程不会被释放,那么这个线程中对应的ThreadLocalMap也就一直不会被回收,如果线程很多的话,那么ThreadLocalMap占用的内存就越来越大,这样的话就可能会出现内存溢出问题。
那么,如何解决呢,其实很简单,每次使用完ThreadLocal中保存的参数后,调用ThreadLocal的remove方法删除即可。