结构变化后带来的好处
早期ThreadLocalMap里的Entry较多,每个线程都有一个,占用空间大
线程销毁时,后期的ThreadLocalMap将会销毁,释放资源
设置默认值,也就是当前线程和ThreadLocal对象还未往ThreadLocalMap里存数据时
ThreadLocal threadLocal2 = new ThreadLocal(){
@Override
protected String initialValue() {
return "hello";
}
};
System.out.println(threadLocal2.get());
因重写了ThreadLocal里的方法,该对象实际上是生成的一个匿名类,并继承了ThreadLocal
我们看看ThreadLocal如何存数据的
获取当前线程的ThreadLocalMap
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的set方法
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];//遍历当前线程的ThreadLocalMap
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
if (k == key) {//发现key,则覆盖value
e.value = value;
return;
}
if (k == null) {//发现某个key对应的ThreadLocal对象已被回收
replaceStaleEntry(key, value, i);//详见下方解析
return;
}
}
tab[i] = new Entry(key, value);//没有找到key,说明是第一次存这个key
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
new Entry
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);//调用WeakReference的构造方法,使ThreadLocalMap里的key与ThreadLocal对象保持弱引用的关系,即不影响该对象的gc
value = v;
}
}
上面的代码中,如果发现ThreadLocalMap里有key为null,会执行replaceStaleEntry(key, value, i);该方法里调用了expungeStaleEntry(int staleSlot)
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;//清理该key对应的value
tab[staleSlot] = null;//清理该key
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);//遍历ThreadLocalMap
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
if (k == null) {//清理key为null的Entry
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);//重新计算hash
if (h != i) {//如果不是当前位置,将移动
tab[i] = null;//置空当前位置的Entry
// 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;
}
上诉方法是可以防止内存泄露,将为null的key对应的Entry清理。
ThreadLocalMap使用的是线性探查法,与此相关的算法有HashMap的拉链法
回收过程
ThreadLocal引用销毁(这个词可能不专业)
ThreadLocal对象因没有强引用,只有个弱引用,gc时将回收。这就是为什么用弱引用,使用强引用时,会影响ThreadLocal对象的回收
如果ThreadLocal对象被回收了,ThreadLocalMap里依旧存着该对象的虚引用为key的Entry(包括Value),这将会造成内存泄漏,如何避免呢?
实际代码中,ThreadLocalMap会在set,get以及resize等方法中对stale slots做自动删除(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)。当然将threadLocal对象设置为null并不能完全避免内存泄露对象,最安全的办法仍然是调用ThreadLocal的remove方法,来彻底避免可能的内存泄露。
ThreadLocal对象可设置为静态变量,这样在任何地方都能方便的获取到该对象,执行remove。