原由:ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
作用:为一个线程保存一个线程本地变量,该变量对本线程全局可见,各个线程之间互不干扰,也互不可见。
源码解析
1、ThreadLocal.Values是ThreadLocal的静态内部类。在ThreadLocal.Values中有一个table成员:
而这个table就以key,value的形式存储了线程的本地变量。key是ThreadLocal<T>类型的对象的弱引用,而Value则是线程需要保存的线程本地变量。
ThreadLocal在Looper中的应用
先来看下ThreadLocal的用法,每个线程都可以调用Looper.prepare()来生成一个Looper对象来处理线程内消息循环,Looper是线程相关的,每个线程的Looper互不干扰。ThreadLocal在Lo在Looper类中,有一个ThreadLocal<Looper>对象。使用ThreadLocal的get和set方法。在prepare中,调用set将Looper存入所在线程的本地变量中,从而将Looper和Thread关联了起来,
Paste_Image.pngThreadLocal的set方法
Paste_Image.pngThreadLocal的get方法
Paste_Image.pngThreadLocal.Values的成员变量
Paste_Image.png被回收的key,在此解释一下,table中,偶数位放key,基数位放value,key就是ThreadLocal的reference成员,如下,它是ThreadLocal对象本身的弱引用。在被回收的index处,将执行table[index] = TOMBSTONE,table[index+1] = null
Paste_Image.pngValues中的put的方法
Paste_Image.pngcleanUp方法的用途,就不贴代码了,cleanUp的作用是,检测现在需不需要rehash,如果需要,则rehash之后直接return,rehash是将oldTable中的还存活的值拷贝到newTable中,因此,rehash之后,newTable中不存在TOMBSTONE以及被回收了的key,若无需rehash,则在被回收了的key所在的index处,执行table[index]=TOMBSTONE,table[index+1]=null,释放不用了的value。
Object getAfterMiss(ThreadLocal<?> key)
getAfterMiss方法由ThreadLocal的get方法调用,当第一次取table中index处的key没有取成功时或者此时线程的Values成员还没初始化时,会进入该方法。该方法大体分成两步:
1,判断table[index] == null是否为true,若为true,则调用key.initialValue获得初始值并赋值给value,下面有个条件语句if (this.table == table && table[index] == null),这个函数的第一句话就是Object[] table = this.table么,这里解释一下,因为这中间调用了key.initialValue(),这个函数是这样定义的:
Paste_Image.png
2, 若table[index] == null为false,则进入循环体,遍历所有索引,继续搜索,若满足table[index] == key.reference,则返回对应得value,若满足table[index] == null,则调用key.initialValue()获取初始化的value,然后又要判断table是否已被改变,此处逻辑与1一致,其中多了一个逻辑:若table[firstTombstone] == TOMBSTONE,那么将key,value放入firstTombstone,firstTombstone + 1的位置并返回value。
当把key,value放在一个table[index]为null的时候,为啥要调用cleanUp?!我在rehash函数的注释中看到了这样一句话:We must rehash every time we fill a null slot; we depend on the presence of null slots to end searches (otherwise, we'll infinitely loop)。而调用rehash的地方只有cleanUp一处
那这句注释又怎么理解呢,对于We must rehash every time we fill a null slot,每当我们在null slot中填入一个key,那么table的size就要增加1,当size+tombstones >= maximumLoad时需要扩容table,因此需要调用rehash。对于we depend on the presence of null slots to end searches (otherwise, we'll infinitely loop),由于扩容会增加null slot的数量,回到getAfterMiss函数,试想,若null slot数量越多,那么table[index] == null条件便越容易满足,getAfterMiss便越早return,也就提高了效率。若不调用rehash,一旦table满了,且table中没有对应得key,将导致无限循环
ThreadLocal之static
回到Looper代码中,可以看到ThreadLocal成员被声明成了static
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
那我不禁要想,为毛线要声明成static,我是这么理解的。
1、一般来说ThreadLocal这个key所对应的value应该是一个线程全局可见的,那么要获得这个变量,用static方法会方便些,就像Looper中的myLooper函数.
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那么sThreadLocal顺利成章被声明成static。
2、我注意到ThreadLocal文件开头有个注释
/**
- Implements a thread-local storage, that is, a variable for which each thread
- has its own value. All threads share the same {@code ThreadLocal} object,
- but each sees a different value when accessing it, and changes made by one
- thread do not affect the other threads. The implementation supports
- {@code null} values.
- @see java.lang.Thread
- @author Bob Lee
*/
public class ThreadLocal<T> {
...
}
其中有句话,意思是所有线程共享同一个ThreadLocal对象,但是当他们访问ThreadLocal所对应的对象时,都能看到不同的值,改变这个值不会影响到其他线程。
问题
1、关于内存泄露问题,多线程条件下,线程池条件下,ThreadLocal不声明成static条件下的内存情况
2、用WeakHashMap代替table是否可行,为什么要重新写一个类似于HashMap功能的table?
网友评论