图解:该图基于Android中的ThreadLocal在Looper中的应用,其能够实现一个线程只有一个Looper的私有实例,左边是通过代码分析得到的类关系图,我们可以看到可以通过线程得到一个Looper,首先通过Thread里的成员变量得到ThreadLocalMap,然后通过Looper中的静态变量能够得到ThreadLocals如下代码,
然后通过ThreadLocals实例得到Map中的Entry然后在通过该实例得到Looper的实例,如下
另外,右图是在该应用中的各种类的引用关系链,通过它我们可以分析到为什么Entry引用key是用的弱引用。我们在android中有可能会用到Looper.quit简单讲就将Looper实例置为null让垃圾回收器回收。此时分析右图,可知,当Looper为null时,ThreadLocal的实例也就没有太大的用处了,也需要进行回收,但是如果Entry引用key是用的强应用,ThreadLocal的实例就不能回收,如果是弱引用的话,就可以在垃圾回收GC的时候强行回收掉。但仅仅是这样还不行,在某些情况下还是会出现问题,这就需要具体情况具体分析,下面看一个来自文章https://mp.weixin.qq.com/s/vURwBPgVuv4yGT1PeEHxZQ的例子:
分析该例子的引用链如下
TestClass的实例置null是想释放int的空间,但是不好意思,我还是能够有引用链到达int,通过Thread->Entry->TestClass->int,可能有人问TestClass实例不是置null了么,TestClass的实例t,只是线程中Thread中的一个引用置null了,也就是说thread->TestClass这条线不可达,但是Thread->Entry->TestClass->int是可达的,所以会内存泄露,与是内存溢出。解决方案是remove函数,即溢出Entry对TestClass的引用,源码如下:
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();
expungeStaleEntry(i);
return;
}
}
}
public void clear() {
this.referent = null;
}