【JUC并发】八股总结三: ThreadLocal

82 阅读3分钟

ThreadLocal底层实现

我的描述。

ThreadLocal 有一个内部类 ThreadLocalMap,这相当于一个hash表。

ThreadLocalMap又作为Thread类对象 的成员,相当于每个 线程都维护了自己的 ThreadLocalMap。

每当往ThreadLocal set值的时候其实是 往当前线程的ThreadLocalMap中添加一个 key,value的映射,key就是ThreadLocal,value就是要保存的变量副本。

ThreadLocal 关键字的作用

ThreadLocal是一个解决线程安全问题的工具,实现了每个线程都有自己的一个空间 存储共享变量(ThreadLocal) 的副本,每个线程去访问操作自己的共享变量副本就行。(有的就线程局部变量)

ThreadLocal 的用处 (重要)

  1. 线程隔离
  2. 上下文传递

项目中有用到ThreadLocal吗(面经)

  1. 配合拦截器,将身份验证信息放入ThreadLocal中,确保线程内任何地方都能安全访问用户信息。 (上下文传递)

ThreadLocal 内存泄露问题

【Java面试最新】ThreadLocal会出现内存泄露吗?_哔哩哔哩_bilibili

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用

所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。

插曲:那Thread线程被回收了,ThreadLocalMap跟着里面的entry 及value 不就也会回收吗?

这样说不完全对,因为当使用线程池的时候,Thread不会被回收而是重复利用,这样就会存在内存泄露, 甚至出现混乱(下一个线程能获取到上一个线程set的值)

但其实,ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。 但最好还是我们手动清理,调用ThreadLocal的remove()

ThreadLocalMap自动清理key为null的数据就不会内存泄露了吗?

答: 不能完全避免那怎么办?

  1. 每次使用完ThreadLocal后,调用remove() 方法手动清理
  2. ThreadLocal申明为全局变量,则不会出现最开始说的(ThreadLocal 没有被外部强引用)的情况。

ThreadLocal还有什么内存泄露的问题吗?

  1. 除了key是弱引用这个情况外。
  2. 搭配线程池使用时,由于线程池复用线程,导致后续线程可能获取到前面线程set的值。

为什么ThreadLocalMap中key要弱引用?(也是为了避免内存泄露)

下面的描述 前提是ThreadLocal在局部创建时

  1. ThreadLocal 为局部变量时(在方法中声明),我们在方法中 set 了值,那么线程中的ThreadLocalMap中的entry的key就会指向这个ThreadLocal。
  2. 如果Entry中的key为强引用,那么当方法执行完毕栈帧销毁(局部变量表中的引用不会继续指向ThreadLocal )方法执行完毕后ThreadLocal不会GC回收,因为线程还存活它被ThreadLocalMap的entry的key强引用了,那么就出现了内存泄露
  3. 所以要使用弱引用,但使用弱引用也会带来一点问题(上面已经说了),但其实,ThreadLocalMap 实现中已经考虑了这种情况。

ThreadLocalMap是如何处理哈希冲突的?

线性探测法(不是拉链法)

为什么?

根据拉链法和线性探测法的优缺点,因为ThreadLocalMap中的key是threadLocal对象,所以可能负载因子不会太高,使用简单的线性探测法就行。

线性探测法和拉链法有什么优缺点?

线性探测法:

  1. 实现简单
  2. 内存连续,缓存友好,很好地利用空间局部性原理。

但是线性探测缺点:

  1. 负载因子高(表满的时候) 时 性能下降严重。

拉链法:

  1. 不会出现负载因子高而性能下降的问题