ThreadLocal 是什么?
是一个关于创建线程局部变量的类。
每个线程都有一个保存值的 ThreadLocalMap 对象,ThreadLocal 的值就存放在了当前线程的 ThreadLocalMap 成员变量中,所以只能在本线程访问,其他线程不能访问。
public class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
从当前线程的ThreadLocalMap中取出当前线程对应的变量的副本【注意,变量是保存在线程中的,而不是保存在ThreadLocal变量中】
363274-20191206104956269-1729288815.pngThreadLocalMap维护了Entry环形数组,数组中元素Entry的逻辑上的key为某个ThreadLocal对象(实际上是指向该ThreadLocal对象的弱引用),value为代码中该线程往该ThreadLoacl变量实际塞入的值。
从ThreadLocal读一个值可能遇到的情况:根据入参threadLocal的threadLocalHashCode对表容量取模得到 index
- 如果index对应的slot就是要读的threadLocal,则直接返回结果
- 调用getEntryAfterMiss线性探测,过程中每碰到无效slot,调用- expungeStaleEntry进行段清理;如果找到了key,则返回结果entry
- 没有找到key,返回null
ThreadLocal的set方法可能会有的情况。
- 探测过程中slot都不无效,并且顺利找到key所在的slot,直接替换即可
- 探测过程中发现有无效slot,调用replaceStaleEntry,效果是最终一定会把key和value放在这个slot,并且会尽可能清理无效slot
-- 在replaceStaleEntry过程中,如果找到了key,则做一个swap把它放到那个无效slot中,value置为新值
-- 在replaceStaleEntry过程中,没有找到key,直接在无效slot原地放entry - 探测没有发现key,则在连续段末尾的后一个空位置放上entry,这也是线性探测法的一部分。放完后,做一次启发式清理,如果没清理出去key,并且当前table大小已经超过阈值了,则做一次rehash,rehash函数会调用一次全量清理slot方法也即expungeStaleEntries,如果完了之后table大小超过了threshold – threshold / 4,则进行扩容2倍
ThreadLocal内存泄漏问题?
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的就可能出现内存泄露 。(在web应用中,每次http请求都是一个线程,tomcat容器配置使用线程池时会出现内存泄漏问题)
网友评论