内存溢出和内存泄漏概念:
内存溢出: Memory overflow 没有足够的内存提供申请者使用.
内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出
ThreadLocal不管是强引用和弱引用都无法解决内存泄漏的问题:
当ThreadLocal中的ThreadLocalMap 中的Entry是强引用对象,那么当ThreadLocal的Entry对象使用完后也就是entry.get()=null,但是因为是强引用,而且Entry对ThreadLocal是强引用,所以是不会被清除的,除非手动删除Entry以及CurrentThread,
在没有手动删除Entry以及CurrentThread(当前线程)依然运行的前提下, 始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏
当ThreadLocal中的ThreadLocalMap 中的Entry是弱引用对象, 没有任何强引用指向threadlocal实例(这里Entry不再强引用ThreadLocal了),此时 entry.get()=null, 所以threadlocal就可以顺利被gc回收
在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry, value就不会被回收, 而这块value永远不会被访问到了(因为key=null), 导致value内存泄漏
造成内存泄漏的主要原因:
主要两个原因
1 没有手动删除这个 Entry
2 CurrentThread 当前线程依然运行
第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法删除对应的 Entry ,就能避免内存泄漏。
第二点稍微复杂一点,由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。
综上, ThreadLocal 内存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除(remove()方法)对应 key 就会导致内存泄漏.
key要使用弱引用
无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。 要避免内存泄漏有两种方式:
1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束
相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的.
也就是说,只要记得在使用完ThreadLocal 及时的调用 remove ,无论 key 是强引用还是弱引用都不会有问题.
事实上,在 ThreadLocalMap 中的set/getEntry方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.
这就意味着使用完 ThreadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏.
ThreadLocal是如何对多线性进行隔离的
ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。
● 斥同步: synchronized 和 ReentrantLock
● 非阻塞同步: CAS, AtomicXXXX
● 无同步方案: 栈封闭,本地存储(Thread Local),可重入代码
ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。
参考资料:https://www.pdai.tech/md/java/thread/java-thread-x-threadlocal.html