ThreadLocal简介
首先介绍下什么是ThreadLocal,官方给出的介绍如下:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
翻译过来就是说:这个类提供了线程内部的局部变量,这些局部变量不同于它们普通的副本,因为每个其的线程(通过get或者set方法)都有自己的独立的变量。一般来说ThreadLocal都是private static的,用于关联线程。(英语比较蹩脚)
总结来说,ThreadLocal的作用就是提供线程内的局部变量,通过它可以在线程内部存储数据,只有在指定的线程中才可以获取到存储到数据,而其他的线程则不可以。用ThreadLocal可以实现一些比较复杂的功能,比如某些数据是以线程为作用域,并且不同线程有不同数据副本的时候,就可以采用ThreadLocal,比如Looper、ActivityThread、AMS等。另一个使用场景就是复杂逻辑下的对象传递,比如监听器的传递。
ThreadLocal源码解析
构造函数
首先来看构造函数:
|
|
内部啥也没做。
initialValue
initialValue函数看名字就知道是用来初始化的:
|
|
该函数在调用get函数的时候会第一次调用,但是如果一开始就调用了set函数,则该函数不会被调用。通常该函数只会被调用一次,除非手动调用了remove函数之后又调用get函数,这种情况下,get函数中还是会调用initialValue函数。
可以看到,该函数是protected类型的,因此建议在子类中重载该函数,所以通常该函数都会以匿名内部类的形势被重载,从而指定初始值。
set
接下来看set方法,ThreadLocal是一个泛型类,只需要分析set和get方法就可以明白其工作原理:
|
|
首先获取到当前线程,然后通过getMap方法获取到当前线程内部的ThreadLocalMap对象,判断如果map不为空就存储下value,key为当前ThreadLocal的引用,否则的话就新建一个map
get
再来看一下get方法:
|
|
也是首先获取到当前的线程,然后获取线程中的ThreadLocalMap对象,如果map不为空的话就获取map中的value并返回,否则就返回setInitialValue(),最终会去调用initialValue方法,也就是获取初始值。
ThreadLocalMap
上面的set和get方法中都出现了ThreadLocalMap这个类。下面就来看一下ThreadLocalMap的官方介绍:
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. No operations are exported outside of the ThreadLocal class. The class is package private to allow declaration of fields in class Thread. To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.
ThreadLocalMap是一个定制的hash map,只适合用来维护当前线程中的值,在ThreadLocal之外不会导出任何值。该类是私有的,允许在线程中声明,哈希表条目使用弱引用,但是只有空间耗尽,才会回收。
看上面get和set方法中的getMap:
|
|
可以看出,ThreadLocalMap在ThreadLocal中定义,而在每个Thread中包含了一个ThreadLocalMap,它的key是ThreadLocal实例本身,而value就是需要存储的Object对象。
在Thread中包含这个成员变量:
|
|
小结一下
ThreadLocal就是在操作Thread中的ThreadLocalMap进行数据的存储。正是因为每个线程中都有ThreadLocalMap,在指定的线程中存储数据以后,只有在指定的线程中可以获取到存储到数据,下面举一个书上的例子:
|
|
三个线程分别操作mThreadLocal对象并且在各自的线程中获取mThreadLocal的值,打印的log如下:
|
|
可以看到,不同的线程之间是互不干扰的。
再深入一下
下面是从网上看到的关于ThreadLocalMap可能引发内存泄漏的一些分析
由上面ThreadLocalMap的介绍可知ThreadLocalMap使用ThreadLocal的弱引用作为key,上文介绍到的一些对象之间的关系如图所示:

如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。
通过阅读ThreadLocalMap的源码发现其实对于上述情况已经有一些防护措施:
ThreadLocalMap的getEntry方法:
|
|
getEntryAfterMiss代码如下:
|
|
expungeStaleEntry代码如下:
|
|
所以getEntry的大致流程就是:
首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;
如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询
在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。而且set操作也是类似的,将key为空的这些Entry都删除,防止内存泄漏。但是上面的思想依赖一个前提:要调用ThreadLocalMap的getEntry或者set函数,这显然不可能在任何条件成立,因此很多情况下需要手动调用remove函数,手动删除不需要的Entry。
所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
以上。