前言
刷八股的时候,ThreadLocal这个地方,觉得有点奇怪,key是弱引用,value是强引用,为什么这样设置呢?
基本原理
首先我们要了解ThreadLocal的实现原理,它的底层实际上是一个类似于hashmap的ThreadLocalMap,通过Thread.currentThread()获取到当前线程的对象后,将之作为key,将要设置的值作为value存储到这个ThreadLocalMap中,而在这过程中,key使用到了弱引用,value则是一个强引用。
key为什么是弱引用而不是强引用
这一点很好理解,ThreadlocalMap是和线程绑定在一起的,如果这样线程没有被销毁,而我们又已经不会在某个threadlocal引用,那么key-value的键值对就会一直在map中存在,这对于程序来说,就出现了内存泄漏。
为了解决这个内存泄漏的问题,需要将key设置为弱引用,那么在一次gc后,弱引用的对象就会被回收。因此key要设置为弱引用。
value为什么要设置为强引用
相信细心的小伙伴已经发现了,如果只是将key设置为弱引用而不将value设置为弱引用,那么value就不会内存泄漏吗?答案当然是会的。
那为什么不设置为弱引用呢?
设想一下这样一个场景,将key和value都设置为弱引用,我们知道key是当前线程的对象,我们在使用的时候,他肯定是有强引用的(比如代码中创建的ThreadLocal对象),那么如果在我们使用过程中如果发生了gc,那么key不会被回收,但是value却会被回收,这样如果后续我们还要get这个value,此时能get到吗?会get到一个null。这必然是我们不希望的。
这一点我们可以举个例子
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
public class Test2 {
public static void main(String[] args) throws Exception {
Map<WeakReference<Integer>, WeakReference<Integer>> map = new HashMap<>(8);
// 注意以下key和value的大小
WeakReference<Integer> key = new WeakReference<>(1);
WeakReference<Integer> value = new WeakReference<>(9999);
map.put(key,value);
System.out.println("put success");
Thread.sleep(1000);
System.gc();
System.out.println("get " + map.get(key).get());
}
}
你觉得上述代码的输出结果是什么?
你可能会回答,两个都是弱引用,一次gc后两个肯定都回收了,get到null?
答案确实是null,但是并不是都被回收了,不知道大家记不记得包装类型的缓存机制,int的包装类型会缓存0-127的引用,因此对于1来说,它的强引用一直存在,不会被回收,所以1的弱引用不会被回收,但是9999的被回收了,所以get到了null。
那大家想一下,把value换成8或者其他小于127的数字,上述代码会不会不一样。
相信大家已经有了答案。
通过这个例子,大家应该对上述ThreadLocal的原理的理解会更透彻。
这个例子来自博客被面试官问懵了,ThreadLocal的key为什么设置成弱引用?_threadlocal的key为什么弱引用-优快云博客
注意事项
那么value要是发生内存泄漏怎么办?ThreadLocal设计的时候已经考虑到了这一点,我们在调用set、get方法以及remove的时候,key为null的键值对会被清理,那么value也就不会内存泄漏了。因此在使用ThreadLocal的时候,记得最后一定要使用remove。
以上是个人见解,如有疏忽欢迎指出交流。