up:: ThreadThreadLocalThreadLocalMap的关系
说明:
(1) 本篇博客介绍ThreadLocal在实际使用中,可能会出现的两个问题:内存泄露和空指针异常;
一:内存泄露问题;
使用ThreadLocal可能会导致内存泄露;内存泄漏:某个对象不再有用,但是占用的内存却不能被回收;
1.使用ThreadLocal,会导致内存泄露的原因;
弱引用特点:如果一个对象只被弱引用关联的话,那么这个对象就可以被垃圾回收器回收;而如【Object obj=new Object(),obj1 = obj2】这些就是强引用;但是需要注意,如果一个弱引用正在被使用(即弱引用被强引用关联时,弱引用是不会被回收的)
那么此时就可以发现:ThreadLocalMap中的键值对中的:key是弱引用,而value则是强引用;那么,这样一来:
(1)正常情况下: 当线程把任务都执行完了,线程终止后,Thread线程就会被JVM回收;那么,线程里面的成员变量ThreadLocalMap也会被回收;那么ThreadLocalMap中的键值对<ThreadLocal<,对象值,也会被回收;
(2)出问题的情况: 但是,如果线程始终不终止,比如我们在使用线程池的时候,同一个线程会反复执行任务;那么,自然只要线程池不关闭,里面的线程是很难被终止的;那么当任务已经执行完了,但线程池却没有关闭,里面的线程没有被终止的时候,那么Thread中的成员变量ThreadLocalMap就会一直存在,那么ThreadLocalMap中的键值对<ThreadLocal<,对象值也会存在;但是,由于键值对<ThreadLocal,对象值中的key是弱引用,所以在这种情况下一个没有被使用的弱引用是可以被回收的;但是,value是强引用,是无法被回收的;由此,就会出现这种情况【任务已经执行完了,但线程池没有关闭,线程池中的线程没有被终止,那么ThreadLocalMap中的键值对的value已经没有被使用了,但是value还一只存在于内存中】;如果,一个线程有很多ThreadLocal的话,那么一个线程就会有很多Value没被回收;由此,就导致内存泄露;
2.JDK应对的策略;
JDK面对这个问题的解决策略:在 ThreadLocalMap中的 set(),remove(),resize(),rehash()等方法中,其会扫描,如果key被置为了null(PS:如果key被垃圾回收了,key就会被置为null),那么其也会把value置为null;
但是,这些 ThreadLocalMap中的 这些方法,需要我们去调用,其才会执行上面的操作;
而且,实际中的情况往往是:如果一个ThreadLocal不再被使用了,我们往往会忘记调用 ThreadLocalMap中的 set(),remove(),resize(),rehash()等方法中;
3.解决ThreadLocal内存泄露的:需要采用的实践策略;(核心)
所以,一种可以采用的实践策略(这也是阿里规约中规定的一条最佳实践)是:我们使用完ThreadLocal后,应该主动调用ThreadLocal的remove()方法;然后,ThreadLocal的remove()方法就会去调用ThreadLocalMap的remove()方法,然后就会强制把【线程为执行当前这一轮任务,而创建的<ThreadLocal,当前任务的对象值,中的key和value】都给置为空;
在实际开发中,比如我们是使用拦截的方式获取到当前请求的用户信息;那么在此次任务执行完毕后,需要调用ThreadLocal的remove()方法,把线程为了处理当前这次请求,而存储在ThreadLocalMap中的<专为保存当前这次请求中的对象而创建的ThreadLocal,当前这次请求的对象值给清除掉;
一个疑惑: 但是,自己就有个疑问:下次请求再来的时候,为了保存请求中同种类型的对象(比如User对象),我们还需要重新在ThreadLocalMap中,创建一个全新的<ThreadLocal<,对象值;PS:这个开销是不是相对来说,可以承受?
二:装箱拆箱错误,而导致的空指针异常;
如下案例:
运行结果:
所以,如果我们把get()方法的返回值类型设为Long,就不汇报空指针异常了: