在聊ThreadLocal之前,首先咱们得先聊一下,强引用,软引用,弱引用与虚引用的问题。
1.强引用
Object o = new Object();这里的o就是强引用,如果引用一直存在,该对象就不会被回收。
2.软引用
A a = new A();
SoftReference<A> soft = new SoftReference<A>(a);
如果通过-Xmx设置最大的堆为3M,上面的soft对象占用2M,后面你再new一个大小为2M的对象,你会发现这个soft对象会自动被回收掉。这就是软引用,当堆内存不够分配空间的时候,会被自动回收。
应用场景是:缓存
3.弱引用
B b = new B();
WeakReference<B> weak = new WeakReference<B>(b);
直接说结论吧,弱引用 只要jvm发生垃圾回收,就会被回收。那么应用场景是什么呢?这就和我们今天讲的ThreadLocal有关系了,主要是防止一些特定情况下发生内存泄露而使用的。具体什么特定情况,下面再说。
4.虚引用
C c = new C();
Queue queue = new Queue();
PhantomReference<C> phantom = PhantomReference<C>(c,queue);
phantom.get();
你会发现phantom.get()得到的值是null,这就是虚引用,那么可能就会有疑问,一new出来就为null,有什么意义呢?其实你发现后面还有一个队列,这个队列就表示将这个虚引用放入到该队列之中,然后起到一个标志性的作用,告诉计算机这里有一个直接内存需要被回收。
例如上图,jvm中的一个引用直接指向堆外内存的区域,那么如果按照正常的java对象回收,jvm只会回收jvm中堆的对象,那么咱们要想回收堆外的对象,就得告诉jvm让jvm告诉计算机去回收,说白了,这是一种特殊情况,所以需要一个标志,虚引用就是起到该作用。
强,软,弱,虚引用都讲完了,现在再来看ThreadLocal
D d = new D();
ThreadLocal<D> tl = new ThreadLocal<D>();
tl.set(d);
以上最后一行代码发生了什么很关键:
set方法不难发现,先取当前线程的TheadLocalMap,然后把this也就是当前的tl对象作为key,然后d为value值。再往下看set方法:
是new了一个Entry对象,并且通过构造函数,存进entry里面了。那么这个Entry又是什么呢?继续往下看:
这里会发现两个问题,上面说的存进ThreadLocalMap并不是Map的子类,而是一个对象,然后通过该对象里面的Entry对象来存储key和value,而且这里Entry又继承了一个弱引用对象,并且在构造函数中将key值传了进去,再说明白一点,这个key最后就变成了一个弱引用对象了,在jvm发生垃圾回收的时候就会被回收。
那么为什么要这样呢?
假如我们的key是强引用,那么如果tl=null的时候这个key也不会被回收,应该thread还存在那么Entry也存在,这样就有内存泄露的风险,因为有的线程是全天24小时在跑的,而这里如果是弱引用,那么在垃圾回收的时候就会自动被清除掉。
那么还有一个问题,虽然这个key是被回收了,但是这个value值还是个强引用,如果线程不死,那么也将一直存在,并且它的key是null,故还是会引起内存泄露问题,这也就是我们常说的ThreadLocal内存泄露。
如何解决?显示的调用remove方法就可以,其实在set和get方法的时候ThreadLocal都会自动清掉key为null值的entry。
最后说一下ThreadLocal的使用场景,举一个例子如果A方法调用B方法,B方法又调用C方法,如果有一个特定的参数,需要从A传到C,最常用的就是加形参,但是有个ThreadLocal我们就可以把这个参数存进去,然后在C中使用即可。如spring的事务@Transactional中开启事务的connection对象就是存在ThreadLocal中。