JUC-ThreadLocal

Thread、ThreadLocal、ThreadLocalMap之间的关系

  • Thread中有ThreadLocal

  •  ThreadLocalMap是ThreadLocal的静态内部类,Entry是ThreadLocalMap的静态内部类
  •  Enry继承WeakReference

 

JVM内部维护了一个线程版的Map

threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象


ThreadLocal的内存泄漏问题及解决

1、使用弱引用避免内存泄漏(发生GC时弱引用一定会被回收,不管内存够不够用)

当线程方法执行完毕后,栈桢销毁强引用tl也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象,

若这个key引用是强引用,就会导致entry key指向的ThreadLcoal对象及value指向的对象不能被gc回收,造成内存泄漏;

若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的坑)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null。

当我们为threadLocal变量赋值,实际上就是当前的Entry往这个threadLocalMap中存放。Entry中的key是弱引用,当threadLocal外部强引用被置为null(tl=null),那么系统gc的时候,根据可达性分析,这个htreadLocal实例就没有任何一条链路能够引用到它,这个threadLocal将会被回,这样一来,ThreadLocalMap中会出现key为null的entry,就没有办法访问这些key为null的entry的value,如果当前线程一直不结束的话(线程池复用),这些key为null的entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> Value永远无法回收,造成内存泄漏。(key=null,value=大对象,容易造成内存泄漏)

当thread运行结束,threadLocal,threadLocalMap,Entry没有引用链可达,在GC的时候都会被回收。

但在实际使用中我们有时候会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建的线程,为了复用线程是不会结束的,所以treadLocal内存泄漏就值得我们小心。

2、手动调用remove()删除

虽然弱引用保证的key指向的threadLocal对象能被及时回收,但是value指向的对象是需要ThreadLocalMap调用get()、set()时发现key为null时才会去回收整个entry 、value,因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remove()来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove(),那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

Thread.get()和ThreadLocal.set()都去检查所有key为null的Entry对象,并调用expungeStaleEntry(i)删除

 从set()、get()和remove()源码可以看出,在threadLocal的生命周期中,针对threadLocal的内存泄漏问题,最终都会通过expungeStaleEntry()去清理掉key为null的脏entry。


总结:

ThreadLocal并不解决线程间共享数据的问题

ThradLocal适用于变量在线程间隔离且在方法间共享的场景

ThreadLocal通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题

每个线程持有一个属于自己专属的Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题

ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocl对象无法回收的问题

set()、get()和remove()都会回收key为null的entry,防止内存泄漏

猜你喜欢

转载自blog.csdn.net/qq_39940205/article/details/120730233
JUC
今日推荐