Java后端技术全栈|快速掌握并发编程---深入学习ThreadLocal( 五 )


Java后端技术全栈|快速掌握并发编程---深入学习ThreadLocal
文章图片
一个Thread中只有一个ThreadLocalMap , 一个ThreadLocalMap中可以有多个ThreadLocal对象 , 其中一个ThreadLocal对象对应一个ThreadLocalMap中一个的Entry(也就是说:一个Thread可以依附有多个ThreadLocal对象) 。
Java后端技术全栈|快速掌握并发编程---深入学习ThreadLocal
文章图片
总结
每个Thread维护一个ThreadLocalMap映射表 , 这个映射表的key是ThreadLocal实例本身 , value是真正需要存储的Object 。
ThreadLocal本身并不存储值 , 它只是作为一个key来让线程从ThreadLocalMap获取value 。
值得注意的是图中的虚线 , 表示ThreadLocalMap是使用ThreadLocal的弱引用作为Key的 , 弱引用的对象在GC时会被回收 。
ThreadLocalMap使用ThreadLocal的弱引用作为key , 如果一个ThreadLocal没有外部强引用来引用它 , 那么系统GC的时候 , 这个ThreadLocal势必会被回收 , 这样一来 , ThreadLocalMap中就会出现key为null的Entry , 就没有办法访问这些key为null的Entry的value 。
Java后端技术全栈|快速掌握并发编程---深入学习ThreadLocal
文章图片
如果当前线程再迟迟不结束的话 , 这些key为null的Entry的value就会一直存在一条强引用链:
ThreadRef->Thread->ThreaLocalMap->Entry->value
永远无法回收 , 造成内存泄漏 。
注意:其实在ThreadLocalMap的设计中已经考虑到这种情况 , 也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value 。
但是如果上述代码中的这行代码
threadLocal.remove();
把注释放开 , 这不会抛出OOM 。
另外 , 网上很多文章都说这是由于弱引用导致的 , 个人认为不能把锅扔给弱引用 , 这和使用者有直接关系 。 如果使用得当是不会出现OOM的 。
由于Thread中包含变量ThreadLocalMap , 因此ThreadLocalMap与Thread的生命周期是一样长 , 如果都没有手动删除对应key , 都会导致内存泄漏 。
但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏 , 对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除 。
因此 , ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长 , 如果没有手动删除对应key就会导致内存泄漏 , 而不是因为弱引用 。
那为什么使用弱引用而不是强引用??
key使用强引用
当ThreadLocalMap的key为强引用回收ThreadLocal时 , 因为ThreadLocalMap还持有ThreadLocal的强引用 , 如果没有手动删除 , ThreadLocal不会被回收 , 导致Entry内存泄漏 。
key使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时 , 由于ThreadLocalMap持有ThreadLocal的弱引用 , 即使没有手动删除 , ThreadLocal也会被回收 。 当key为null , 在下一次ThreadLocalMap调用set(),get() , remove()方法的时候会被清除value值 。


推荐阅读