ThreadLocal原理(基础款)
ThreadLocal它提供了线程局部变量的功能。它的原理可以简单概括为以下几点:
- 每个Thread对象都维护了一个以ThreadLocal为键、任意类型对象为值的Map。这个Map存储了每个线程对应的局部变量值,这个Map叫做ThreadLocalMap。
- 当通过ThreadLocal对象调用
set()
方法设置变量值时,实际上是获取当前线程的ThreadLocalMap将当前ThreadLocal对象作为键,变量值作为值,存储到Map中。
scss
复制代码
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对象为键获取对应的值。
ini
复制代码
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代码结构
scala
复制代码
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 的值可以避免内存泄漏问题,确保程序的性能和稳定性。