ThreadLocal的定义
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被static final
修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
final: 确保 ThreadLocal 变量不可变,即一旦初始化后就不能再指向另一个 ThreadLocal 实例。
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal = new ThreadLocal<>();//final 修饰编译过不去,不能再指向另一个实例
static: 使得 ThreadLocal 成为类级别的成员,所有实例共享同一份 ThreadLocal 变量
原理:Thread维护ThreadLocal与实例对象的映射
ThreadLocal是怎么达到线程隔离的呢?
以下源码片段可以看出存放对象映射关系是存放在Thread类中的threadLocals变量,存储类型是自定义的ThreadLocalMap
由ThreadLocal的get、set方法可以看出,map对象的key是ThreadLocal,value是ThreadLocalMap.Entry.value
//存放ThreadLocal与实例对象的映射变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class ThreadLocal<T> {
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
存储分析
Entry是什么
看构造器可以看出ThreadLocal变量k被传递到了WeakReference的构造函数里面,也就是说key为ThreadLocal对象的弱引用,具体是referent变量引用了ThreadLocal对象,value为强引用,具体是调用ThreadLocal的set方法传递的值。
为什么要对key弱引用
使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免ThreadLocal 不能被回收而造成的内存泄漏的问题。但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。
怎么避免内存泄露
ThreadLocalMap提供了set,get,remove方法在一些时机下会对这些Entry项进行清理,但是这是不及时的,也不是每次都会执行的,所以在使用完毕后即使调用remove方法才是解决内存泄露主要办法
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();//清除key
expungeStaleEntry(i);
return;
}
}
}
//清除value=null的对象
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}