threadlocal原理及常用应用场景(一张图看懂ThreadLocal原理)(1)

图解:该图基于Android中的ThreadLocal在Looper中的应用,其能够实现一个线程只有一个Looper的私有实例,左边是通过代码分析得到的类关系图,我们可以看到可以通过线程得到一个Looper,首先通过Thread里的成员变量得到ThreadLocalMap,然后通过Looper中的静态变量能够得到ThreadLocals如下代码,

threadlocal原理及常用应用场景(一张图看懂ThreadLocal原理)(2)

然后通过ThreadLocals实例得到Map中的Entry然后在通过该实例得到Looper的实例,如下

threadlocal原理及常用应用场景(一张图看懂ThreadLocal原理)(3)

另外,右图是在该应用中的各种类的引用关系链,通过它我们可以分析到为什么Entry引用key是用的弱引用。我们在android中有可能会用到Looper.quit简单讲就将Looper实例置为null让垃圾回收器回收。此时分析右图,可知,当Looper为null时,ThreadLocal的实例也就没有太大的用处了,也需要进行回收,但是如果Entry引用key是用的强应用,ThreadLocal的实例就不能回收,如果是弱引用的话,就可以在垃圾回收GC的时候强行回收掉。但仅仅是这样还不行,在某些情况下还是会出现问题,这就需要具体情况具体分析,下面看一个来自文章https://mp.weixin.qq.com/s/vURwBPgVuv4yGT1PeEHxZQ的例子:

threadlocal原理及常用应用场景(一张图看懂ThreadLocal原理)(4)

分析该例子的引用链如下

threadlocal原理及常用应用场景(一张图看懂ThreadLocal原理)(5)

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;

}