『Java面经』ThreadLocal 实现原理是什么 & 有哪些引用类型及使用场景?
Posted 一枚方糖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『Java面经』ThreadLocal 实现原理是什么 & 有哪些引用类型及使用场景?相关的知识,希望对你有一定的参考价值。
1、作用
- ThreadLocal 在多线程环境中,
安全的保存线程本地变量
,同一线程在某地保存数据,任意地方均可获取。 - 同一个ThreadLocal ,由于每个线程都有自己的 ThreadLocalMap ,所以
不同线程中保存的数据互不影响
。
2、保存步骤
- 每个线程中都有一个 ThreadLocalMap 变量
threadLocal
来保存 ThreadLocal 中的数据。 - 在
threadLocals.set()
中,先通过Thread.currentThread()
获取当前线程。 - 通过
getMap(Thread t)
获取当前线程的ThreadLocalMap
对象。 - 当前的
threadLocal
作为 key,保存的数据
作为 value,构造一个Entry
对象保存在ThreadLocalMap 中。 - 调用
threadLocal.get()
取数据也是先获取到当前线程,以 threadLocal 为 key 从这个 ThreadLocalMap 中取出数据。
3、原理 / 内存泄漏
3.1 数据结构
- ThreadLocal 保存一个 value 时,会构造一个 Entry 插入到 ThreadLocalMap 中。
- Entry 继承
弱引用类
,key 指向 ThreadLocal 的弱引用,value 是强引用。
3.2 设计原理
对于一个 ThreadLocal 对象,通常会有两个引用指向它:
- ①线程中声明的
threadLocal
变量,强引用 - ②线程底层 ThreadLocalMap 中键值对的
key
,弱引用 - 不需要使用 ThreadLocal 对象时,将变量 threadLocal 置空,让GC回收 ThreadLocal 对象
- 但是 key 也引用了 ThreadLocal 对象,这个 key 如果是强引用,ThreadLocal 对象就永远不会被回收,但这里设计为弱引用,所以GC 发现 ThreadLocal 对象就会回收它。
- 但是 ThreadLocalMap 中的
value
是强引用,value 中的对象不会回收,造成内存泄漏。 - 最佳方法:使用完 ThreadLocal 对象后,主动调用
remove()
方法,清理对象。
3.3 value如何清理
key 是弱引用,当不存在外部强引用时,会被自动回收。而 value 是强引用,引用链如下
Thread -> ThreadLocal -> Entry -> value
所以只有当 Thread
被回收,value 才会被回收,否则 value 将一直存在,但是让每个线程关闭,是不现实的。在线程池中,大部分线程会伴随着系统的整个周期,那么 value 可能会造成泄漏。
解决方法,在 ThreadLocalMap 进行set(),get(),remove()
的时候,都进行清理:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
//如果找到key,直接返回
return e;
else
//如果找不到,就会尝试清理(说明key不在了,也就是threadLocal = null)
//换句话说,这个数据用不上了
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
// 整个e是entry ,也就是一个弱引用
ThreadLocal<?> k = e.get();
//如果找到了,就返回
if (k == key)
return e;
if (k == null)
//如果key为null,说明弱引用已经被回收了
//那么就要在这里回收里面的value了
expungeStaleEntry(i);
else
//如果key不是要找的那个,那说明有hash冲突,这里是处理冲突,找下一个entry
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
真正回收 value 的是 expungeStaleEntry()
方法,在 remove 和 set 方法中都会调用这个方法。
ThreadLocal 为了避免内存泄露,不仅使用了弱引用维护 key ,还在每个操作上检查 key 是否被回收,进而再回收value。
3.4 总结
- ThreadLocal 可能发生内存泄漏。
- 如果一直没机会调用,set get remove 方法,内存泄漏依旧会发生。
- 所以养成良好的习惯,用完 ThreadLocal 变量 就主动 remove 它。
4、四种引用类型
1、强引用
- Object obj = new Object(),除非显示将 obj 置空,否则GC不会回收。
- 应用场景:对象一般状态。
2、软引用
- 内存充足,不回收;只有内存不足,再回收。
SoftReference<String> softReference = new SoftReference<String>(str);
- 应用场景:对象缓存。
3、弱引用
- 比软引用更弱,被GC扫描到直接回收。
- 应用场景:对象缓存。
4、虚引用
- 虚引用不会决定对象的生命周期。如果一个对象只有虚引用,那么和没引用一样,随时被回收。
- 应用场景:用来跟踪对象被GC回收的活动
5、微信关注『方糖算法』
各类面试资料、内推资源,关注微信公众号获取哦。
以上是关于『Java面经』ThreadLocal 实现原理是什么 & 有哪些引用类型及使用场景?的主要内容,如果未能解决你的问题,请参考以下文章