ThreadLocal的学习
- ThreadLocal的学习
- 1.ThreadLocal是什么?
- 2.ThreadLocal的数据结构
- Java的四种引用类型
- 3.ThreadLocal为什么会出现内存泄露?
- 既然会出现内存泄露为什么Entry的key还要使用弱引用?
- 如何避免内存泄露?
ThreadLocal的学习
1.ThreadLocal是什么?
ThreadLocal对象可以提供局部变量,每个线程Thread拥有一份自己的副本变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰。
2.ThreadLocal的数据结构
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadlocals
也就是说每个线程都有一个自己的ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals=null;
ThreadLocalMap中维护了k-v形式的Entry对象
其中key视为ThreadLocal,value就是我们在ThreadLocal中存储的值
ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中没有链表结构
注意:
key并不是ThreadLocal本身,而是它的一个弱引用(因为继承自
WeakReference
)由此,ThreadLocal本身是不存储值的,我们在使用其对应的set和get方法的时候都是操作的其对应的ThreadLocalMap对象。
也就是说每个线程在往ThreadLocal里存值的时候都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key。这就是线程隔离。
Java的四种引用类型
Java中由值类型也有引用类型,引用类型一般是针对Java中的对象来说的。
Java为引用类型专门定义了一个类叫做Reference。
Reference是跟Java垃圾回收机制息息相关的类。
-
强引用 Strong Reference
Java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用
Object obj=new Object()
我们new了一个Object对象并将其赋值给obj,这个boj就是new Object()的强引用
强引用的特征就是只要有强引用存在,被引用的对象就不会被回收(也就是new Object()不会被回收)
-
软引用 Soft Reference
只有在内存不足的情况下,被引用的对象才会被回收
public class SoftReference<T> extends Reference<T>{ public SoftReference(T referent) { /**/ } public SoftReference(T referent, ReferenceQueue<? super T> q) { /**/ } }
SoftReference继承自Reference,有两种构造函数
T referent:就是软引用对象
ReferenceQueue<? super T> q:就是用来存储封装的待回收的Reference对象,eferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。
-
弱引用 Weak Reference
弱引用和软引用类似,但是弱引用的对象(即被引用的对象)只要垃圾回收执行不管内存是否充足都会被回收(只有弱引用的时候)
-
虚引用 Phantom Reference
虚引用是最弱的引用,引用的是需要被垃圾回收的对象
虚引用中唯一的作用就是用队列接收对象即将死亡的通知
所以在类的定义中,get一直返回的都是null
PhantomReference只有一个构造函数并且必须传入ReferenceQueue
public class PhantomReference<T> extends Reference<T>{ public PhantomReference(T referent, ReferenceQueue<? super T> q){ /**/ } }
ReferenceQueue<? super T> q:就是用来存储封装的待回收的Reference对象。
虚引用跟踪垃圾回收器(gc)收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理(这个和软引用是不一样的)
当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
3.ThreadLocal为什么会出现内存泄露?
在当前线程正在运行的时候,如果发生GC,此时ThreadLocal对象没有被其它地方强引用的时候(即只有弱引用),key指向的ThreadLocal的虚引用就会立即断开(因为弱引用指向的对象被垃圾回收了)
这时就会出现ThreadLocalMap中存在key为null的Entry
并且只要当前线程不结束,该ThreadLocalMap对象就会一直存在,无法回收(因为ThreadLocalMap还存在强引用:value本身就存着一个强引用对象)
此时就导致了内存泄露
既然会出现内存泄露为什么Entry的key还要使用弱引用?
为什么要用弱引用呢?
因为假设我们让key强引用ThreadLocal会导致该对象用永远无法gc
如何避免内存泄露?
其实ThreadLocalMap在设计时采取了一些措施来避免这种key为null、value不为null的对象占用内存
具体措施就是在我们调用ThreadLoca的set、get、remove方法时都会将这些key为null的对象清掉,避免因无法回收而导致内存泄露
如果没有及时使用remove方法会导致什么问题?
-
假设分配了ThreadLocal对象但是并没有执行get、set、remove方法会导致不能有效地清除null对象
-
因为ThreadLocal时属于某个线程的,而在使用线程池的情况下,这些线程都是可重复利用的、存活时间长的线程,因此不及时使用remove方法不仅会导致内存泄露问题,还会引发一些功能逻辑问题
例如,B请求和A请求分配到了线程池中的同一个线程,那么他们拿到的ThreadLocal可能是一样的