ThreadLocal 面试总结 - 吊打面试官

前言

🙌🏻最近有小老弟在问码长关于ThreadLocal的知识点,趁此,我也总结一波,即问即答

1、ThreadLocal到底是干什么的呢?

就是为了做线程隔离的,线程可以理解成人
大壮、小帅、阿美三人都想要HUAWEI Mate 50,一人买一部不得了嘛。但是注意,不要把HUAWEI Mate 50定义成static,否则所有的引用都指向一个对象,就不是每个线程的变量副本了,至于为什么,自己动脑子想一想

2、ThreadLocal它本身会存储数据吗?

并不存储。可以理解为ThreadLocal更像是一个本地线程的操控台,或者像一座桥、像一个引用。线程Thread中属性ThreadLocalsThreadLocal.ThreadLocalMap的管理者,ThreadLocal用于给每个线程操作自己线程的本地变量,线程的本地变量是存放在线程实例的属性ThreadLocalMap上的,ThreadLocalMap本质上就是一个HashMap,但是Java的开发人员压根就不想你用ThreadLocalMap,可以看到他是一个内部类,并且是protected修饰的

3、ThreadLocal底层的数据结构是什么?

就是一个hash
Hash表是个什么玩意儿?
– – 因为我们数组的下标是通过hash值计算得来的,所以,当前这个数组称为Hash

4、ThreadLocal底层中的Hash数组默认长度是多少

16 – 看成hashMap就行,默认16

防止记混:顺便梳理一下ArrayList
ArrayList数组,默认长度为0的空数组,第一次add时会进行扩容,第一次扩容的长度是10,之后按照1.5倍的的增速(增加50%)进行扩容。ArrayList不像HashMap,它没有所谓的负载因子,它只有在elementData装不下时才会进行扩容。ArrayList只会扩容,不会缩容,即使clear了,数组的长度依然不会变。

5、当容量达到多少的时候触发扩容?

扩容因子:2/3

6、ThreadLocal如何处理hash冲突?

线性探测法

7、ThreadLoacl的hashcode是通过一个魔数计算得来的?

也就是斐波那契散列求出来的
魔数 = 黄金分割比 (0.618)*2^32

8、ThreadLocalMap数组长度为什么一定要是2的幂次方?

为了减少hash冲突

9、Java中有几种引用类型?

  • Object obj = new Object();当对象被强引用关联的时候,不管你內存充不充足,垃圾回收的时候都不会
    回收

  • :当垃圾回收的时候,如果内存充足,则不回收该对象,如果内存不足了,则会回收

  • :当垃圾回收的时候,不管内存充不充足,都会回收

  • 一般用于跟踪对象是否可达,不可达则回收。“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。你声明虚引用的时候是要传入一个queue的。当你的虚引用所引用的对象已经执行完finalize函数的时候,就会把对象加到queue里面。你可以通过判断queue里面是不是有对象来判断你的对象是不是要被回收了【这是重点,让你知道你的对象什么时候会被回收。因为对普通的对象,gc要回收它的,你是知道它什么时候会被回收】

10、ThreadLocal中的key值为什么要用弱引用呢?

首先我们要知道,ThreadLocal 是存放在堆里边的

【key 使用强引用:】
对ThreadLocal对象实例的引用被置为null了,但是ThreadLocalMap还持有这个ThreadLocal对象实例的强引用,如果没有手动删除,ThreadLocal的对象实例不会被回收,导致Entry内存泄漏。

【key 使用弱引用:】
对ThreadLocal对象实例的引用被被置为null了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal的对象实例也会被回收。value在下一次ThreadLocalMap调用set,get,remove都有机会被回收。

由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障。

11、那为什么还会出现内存泄漏呢?

因为value依然是强引用,也就是说ThreaLocal他作为key,放到ThreadLocalMap的时候,如果被手动设置了null,那这个value不就成了垃圾对象。当然,value是随当前线程Thread 同生同死,线程结束,当前的这个value也会被销毁

12、为什么value不用弱引用?

如果value为弱引用,key还在的话,get(key) = null,会导致空指针异常

13、怎么解决内存泄漏的问题?

remove() 方法
所以,当时使用完后,一定记得在finally中调用remove()方法,手动释放对keyvalue的引用,避免产生内存泄漏。

14、ThreadLocal怎么使用呢??

// 创建对象
ThreadLocal girl = new ThreadLocal()
girl.get();
girl.set();
girl.remove();
// 跟map用法有什么差别
Map map = new HashMap();
map.put("key",value);
map.get("key");

15、为什么 Threadlocal建议使用 private static final 修饰?

JDK建议ThreadLocal定义为private static final,这样ThreadLocal的弱引用问题则不存在了
1、final :防止实例的引用不被替换,不被替换就不会被GC回收
2、static
— 1️⃣表示为类属性,只有在程序结束才会被回收,因为ThreadLocalMap<ThreadLocal, Object>中的ThreadLocal对象作为key是弱引用,会被GC回收。主要目的就是为了减少内存泄漏 - 同类生、同类死;
— 2️⃣因为threadLocal他也只是一个引用,指向着不同entry里的key而已,所以搞一个就可以了,这也符合static的思想

  • 如果ThreadLocal定义的是线程变量,当线程运行完成,多线程必然在线程池中,核心线程会一直存活,会一直保持对ThreadLocalvalue的引用,由于key采用弱引用,而value是强引用。所以,ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而value 不会被清理掉。这样的话,ThreadLocalMap 中就会出现 key null Entry。假如我们不做任何措施的话,value 永远无法被GC回收,这个时候就可能会产生内存泄露。
  • 如果用Static修饰,那ThreadLocal对象会一直存活,减少了内存泄漏的可能,所以finallyremove() 显得尤为重要

16、ThreadLocal的什么情况会产生脏数据?

主要还是因为用完没有及时remove()的原因,因为在线程池中肯定会存在线程复用,那么和当前Thread绑定的Map不显式调用remove()清除存储相关的信息,下一个线程进来拿到的还是上个线程设置的资源,这并不是他想要的数据,所以finallyremove() 显得尤为重要

17、ThreadLocal 的经典使用场景总结?

数据库连接和 session 管理等。

1、保存线程上下文信息,在任意需要的地方可以获取
2、线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失
3、每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的
4、ThreadLocal无法解决共享对象的更新问题,也不需要解决,设计如此。如果想深究,有个东西叫inheritThreadLocals用来解决父子线程间共享线程变量的问题【InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每个线程拥有它自己的值,与ThreadLocal不同的是,InheritableThreadLocal允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。使用ThreadLocalInheritableThreadLocal传递上下文时,需要注意线程间的切换、异常处理等】

18、✨再补充一点?

小老弟对码长说:ThreadLocal无法解决共享对象的更新问题我不是很理解,既然如此,那我就🙋‍♂️个🌰子:
在这里插入图片描述

公司有项福利,每天打卡最早的十个人,会发放一张券,你可以兑换咖啡、奶茶、饮料以及小蛋糕,当然,不要券的也可以等额兑换成钱。

每个人对券的使用方式不同,有的喜欢喝咖啡,有的喜欢喝奶茶,有的只喜欢钱。

假设把人看成线程

大壮这个人只喜欢喝咖啡,他就把程序改了,每天不发券了,直接送一杯咖啡;小美也这么想,小美呢只喜欢喝奶茶,就把券改成了奶茶。

等到第二天大壮早早打卡的时候气坏了,想要的是咖啡,结果是奶茶。

这表明什么?多线程导致的线程安全问题。

所以,有没有一个共享变量,设置统一的初始值,然后每个线程对这个值的修改是独立的。

答案是有的,很明显,共享变量就是这个ThreadLocal对象,券就是初始值,然后每个人对这个券有自己的想法,也就是set操作,券用完了,也就是remove了


简单来讲,同一线程变量共享,不同线程之间独立。


  • 0
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼码长

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值