ThreadLocal原理,内存泄露到底是怎么回事,干货满满
ThreadLocal原理(基础款)
ThreadLocal它提供了线程局部变量的功能。它的原理可以简单概括为以下几点:
- 每个Thread对象都维护了一个以ThreadLocal为键、任意类型对象为值的Map。这个Map存储了每个线程对应的局部变量值,这个Map叫做ThreadLocalMap。
- 当通过ThreadLocal对象调用
set()
方法设置变量值时,实际上是获取当前线程的ThreadLocalMap将当前ThreadLocal对象作为键,变量值作为值,存储到Map中。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
3. 当通过ThreadLocal对象调用get()
方法获取变量值时,实际上是获取当前线程的ThreadLocalMap,再以以当前的ThreadLocal对象为键获取对应的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通过这种方式,ThreadLocal实现了线程间的数据隔离,每个线程都可以独立地访问和修改自己的局部变量值,而不会影响其他线程的变量。这在多线程编程中非常有用,可以避免线程间的数据竞争和冲突,提高程序的并发性能和可靠性。
TheadLocal跨线程的传递
ThreadLocal 的值是在每个线程中独立存储的,其他线程无法直接获取到该值。但是,你可以通过一些手段将 ThreadLocal 的值从一个线程传递到另一个线程,例如:
- 手动传递:可以在一个线程中获取 ThreadLocal 的值,然后将该值作为参数传递给另一个线程。这需要你自己管理传递过程和确保线程安全。
- 当你需要在父线程中设置一个 ThreadLocal 值,并使其在子线程中可用时,可以使用
InheritableThreadLocal
类型的 ThreadLocal 对象。InheritableThreadLocal
提供了一种方便的方式,在父线程和子线程之间传递 ThreadLocal 值,原理在父线程创建子线程时,线程初始化方法会将父线程的InheritableThreadLocal
传递给子线程
需要注意的是,使用 ThreadLocal 进行跨线程传递值时,要确保线程安全性和正确性。在传递值的过程中,需要注意值的复制或引用传递、值的可变性、值的生命周期等问题,以避免意外的副作用和错误。
总之,虽然 ThreadLocal 的值不会自动在线程间传递,但你可以通过手动传递或使用特定的 ThreadLocal 子类来实现跨线程传递值的需求。在实际应用中,请注意线程安全性和正确性的考虑。
内存泄露原因
先从WeakReference说起
众所周知,java四种引用类型,强,软,弱,虚,先搞明白WeakReference什么时候会被gc清理,网上大多数的说法都是当GC时弱引用对象会被清理,但是需要前提条件,这点很重要!!
jdk文档上写的大意就是噢,如果一个对象没有强、软引用(也要根可达),只通过弱引用(根)可达,那就可以被回收。
先搞几个测试类
weakAndSick为类变量,强引用在所以没被回收掉
强引用不在所以被回收掉
局部变量表中weakAndSickInner仍存在,存在根可达的强引用,所以回收不掉
method方法结束,局部变量引用断开,可以被回收
看看ThreadLocalMap代码结构
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
就是说entry继承WeakReference,也就是key是弱引用类型的,key对应的是ThreadLocal对象。 所以作者在这里使用弱引用类型,就是希望在ThreadLocal对象不再被使用时,ThreadLocalMap中的key中引用的ThreadLocal也就没有意义了,不要影响对ThreadLocal对象的垃圾回收!
内存泄露是针对ThreadLocalMap的,当然如果thread是临时线程用完就丢弃了,那自然可以回收ThreadLocalMap,但是如果是线程池中的常驻内存的线程呢?作为WeakReference的ThreadLocal对象可以被回收么? 实际上我们一般都将ThreadLocal对象作为一个类变量长期使用,所以依然存在强引用,在gc时仍不能回收。如果将这个强引用断开那么ThreadLocalMap的Key就可以被回收了,但是Value依然被强引用着,不能被回收。 所以在一些特殊场景下,也会有不一样的泄露的问题。 比如一个war包被部署到Tomcat中,Tomcat会加载包中的类,然后创建Servlet\Filrer这些类实例. Tomcat维护了一个线程池,每当有请求到来,这些线程对象就会去执行用于处理请求的task,也就是Filter\Servlt这些东西. 当一个war包被卸载时,Tomcat也应该释放所加载的Class和创建的Servlet/Filter实例,使之能被GC回收。 但是
一个类被GC回收需要保证以下条件都不成立:
- objects of that class are still reachable.
- the Class object representing the class is still reachable
- the ClassLoader that loaded the class is still reachable
- other classes loaded by the ClassLoader are still reachable
而ThreadLocal还保留着对象实例,导致类对象不能卸载,也会引起内存泄露。
避免 ThreadLocal 内存泄漏
- 了解使用场景:使用 ThreadLocal 时要了解其适用范围和生命周期,并确保在合适的时机清理和释放相关的值,及时调用
remove()
方法。 - 避免过度使用 ThreadLocal:仔细评估是否真正需要使用 ThreadLocal,尽量避免过度依赖它,以减少潜在的内存泄漏风险。
总之,虽然 ThreadLocal 在多线程编程中非常有用,但需要谨慎使用和管理。正确地使用和清理 ThreadLocal 的值可以避免内存泄漏问题,确保程序的性能和稳定性。
转载自:https://juejin.cn/post/7235117005737427001