ThreadLocal是为了解决对象不能被多线程共享访问的问题,通过ThreadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这样每个线程使用自己的对象实例,彼此不会影响达到隔离的作用,从而就解决了对象在被共享访问带来线程安全问题。
内存泄漏问题
每个线程都持有一个threadLocals实例,threadLocals的生命周期同线程个生命周期一样长。只要线程还存活,threadLocals实例就会被当前线程一直以强引用的方式持有。但是threadLocals的Key是以弱引用的方式被Entry所持有的,系统GC时,会被回收。所以会存在Entry的key为空但是value不为空的情况。Entry的key为空,导致Entry的value无法被访问到,但又无法被回收,所就内存泄漏了。
优化
在ThreadLocal的set和get方法中都有相应的处理。针对key为null的entry,有一定概率会清除其对应的无用的value。
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i); // 这一步会对key=null,value!=null的场景中的value值做清空
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
为什么使用弱引用
如果使用强引用,在业务代码中执行threadLocalInstance==null操作,以清理掉threadLocal实例的目的,但是因为threadLocalMap的Entry强引用threadLocal,因此在gc的时候进行可达性分析,threadLocal依然可达,对threadLocal并不会进行垃圾回收,依然会内存泄漏。相对而言,使用弱引用虽然有存在内存泄漏的可能,但是代码上已经做了优化来保证内存泄漏的发生概率尽可能低。
网友评论