前言
🙌🏻最近有小老弟在问码长关于ThreadLocal
的知识点,趁此,我也总结一波,即问即答
1、ThreadLocal到底是干什么的呢?
就是为了做线程隔离的,线程可以理解成人
大壮、小帅、阿美三人都想要HUAWEI Mate 50
,一人买一部不得了嘛。但是注意,不要把HUAWEI Mate 50
定义成static
,否则所有的引用都指向一个对象,就不是每个线程的变量副本了,至于为什么,自己动脑子想一想
2、ThreadLocal它本身会存储数据吗?
并不存储。可以理解为
ThreadLocal
更像是一个本地线程的操控台,或者像一座桥、像一个引用。线程Thread
中属性ThreadLocals
即ThreadLocal.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()
方法,手动释放对key
和value
的引用,避免产生内存泄漏。
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
定义的是线程变量,当线程运行完成,多线程必然在线程池中,核心线程会一直存活,会一直保持对ThreadLocal
和value
的引用,由于key
采用弱引用,而value
是强引用。所以,ThreadLocal
没有被外部强引用的情况下,在垃圾回收的时候,key
会被清理掉,而value
不会被清理掉。这样的话,ThreadLocalMap
中就会出现key
为null
的Entry
。假如我们不做任何措施的话,value
永远无法被GC
回收,这个时候就可能会产生内存泄露。 - 如果用
Static
修饰,那ThreadLocal
对象会一直存活,减少了内存泄漏的可能,所以finally
里remove()
显得尤为重要
16、ThreadLocal的什么情况会产生脏数据?
主要还是因为用完没有及时
remove()
的原因,因为在线程池中肯定会存在线程复用,那么和当前Thread
绑定的Map
不显式调用remove()
清除存储相关的信息,下一个线程进来拿到的还是上个线程设置的资源,这并不是他想要的数据,所以finally
里remove()
显得尤为重要
17、ThreadLocal 的经典使用场景总结?
数据库连接和 session
管理等。
1、保存线程上下文信息,在任意需要的地方可以获取
2、线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失
3、每个线程往ThreadLocal
中读写数据是线程隔离,互相之间不会影响的
4、ThreadLocal
无法解决共享对象的更新问题,也不需要解决,设计如此。如果想深究,有个东西叫inheritThreadLocals
用来解决父子线程间共享线程变量的问题【InheritableThreadLocal
类是ThreadLocal
类的子类。ThreadLocal
中每个线程拥有它自己的值,与ThreadLocal
不同的是,InheritableThreadLocal
允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。使用ThreadLocal
和InheritableThreadLocal
传递上下文时,需要注意线程间的切换、异常处理等】
18、✨再补充一点?
小老弟对码长说:ThreadLocal
无法解决共享对象的更新问题我不是很理解,既然如此,那我就🙋♂️个🌰子:
公司有项福利,每天打卡最早的十个人,会发放一张券,你可以兑换咖啡、奶茶、饮料以及小蛋糕,当然,不要券的也可以等额兑换成钱。
每个人对券的使用方式不同,有的喜欢喝咖啡,有的喜欢喝奶茶,有的只喜欢钱。
假设把人看成线程
大壮这个人只喜欢喝咖啡,他就把程序改了,每天不发券了,直接送一杯咖啡;小美也这么想,小美呢只喜欢喝奶茶,就把券改成了奶茶。
等到第二天大壮早早打卡的时候气坏了,想要的是咖啡,结果是奶茶。
这表明什么?多线程导致的线程安全问题。
所以,有没有一个共享变量,设置统一的初始值,然后每个线程对这个值的修改是独立的。
答案是有的,很明显,共享变量就是这个ThreadLocal对象,券就是初始值,然后每个人对这个券有自己的想法,也就是set操作,券用完了,也就是remove了
简单来讲,同一线程变量共享,不同线程之间独立。