ThreadLocal
作用
实现线程范围内的局部变量,即ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的。
原理
- ThreadLocal存入值时使用当前ThreadLocal实例作为key(并不是以当前线程对象作为key),存入当前线程对象中的 ThreadLocalMap中去。
ThreadLocalMap属性介绍
- 和普通 Hashmap类似存储在一个数组(Entry类型的数组)内,但与 hashmap使用的拉链法解决散列冲突不同的是 ThreadLocalMap使用开放地址法
- 数组 初始容量16,负载因子2/3
- node节点 的 key封装了弱引用用于回收
ThreadLocalMap中Key(引用、内存泄漏)
key的引用问题(两种泄露)
(当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。)
- 如果key使用强引用
1.业务代码中使用完ThreadLocal ,threadLocal Ref被回收了
2.因为threadLocalMap的Entry强引用了threadLocal,造成threadLocal无法被回收
3.在没有手动删除这个Entry以及CurrentThread依然运行的前提下,Entry就不会被回收(Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏 --------
key引起的内存泄漏
- 如果key使用弱引用
1.业务代码中使用完ThreadLocal ,threadLocal Ref被回收了
2.由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadlocal实例,所以threadlocal就可以顺利被 gc回收,此时Entry中的key=null,但是对应的value的引用是强引用。正常情况下,当线程结束,Value就没有了任何强引用,此时可以正常垃圾回收掉。 3.在没有手动删除这个Entry以及 CurrentThread依然运行的前提下,即线程迟迟不结束,而Value早就不再使 用,由于Entry的强引用,Value不会被垃圾回收掉,导致value内存泄漏 -----------
Value引起的内存泄漏
由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障。
出现内存泄漏的真实原因
1.没有手动删除对应的Entry节点信息
2.ThreadLocal 对象使用完后,对应线程仍然在运行
避免内存泄漏的方式
- 使用完ThreadLocal,调用其remove方法删除对应的Entry
(虽然ThreadLocal有expungeStaleEntry()方法清除key为null的元素。但是可以看出循环退出的条件为遇到null的元素,因此null之后的并且key为null的元素无法被清除。并且这种清除方式是不及时的。)
- key弱引用
- 及时调用set、get方法(ThreadLocal 设计在执行 set、get、remove 等方法时,会扫描 key 为 null 的 Entry,如果发现某个 Entry 的 key 为 null,则代表它所对应的 value 也没有作用了,所以它就会把对应的 value 置为 null,这样,value 对象就可以被正常回收了。)
ThreadLocal与synchronized的区别
1、Synchronized用于线程间的数据共享,而 ThreadLocal则用于线程间的数据隔离。
2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而 ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。