你真的掌握TheadLocal了吗?

142 阅读4分钟

什么是ThreadLocal?

ThreadLocal是Java中的一个线程本地变量,它提供了一种线程安全的方式来存储线程局部变量。每个线程都有自己的ThreadLocal变量副本,线程之间互不干扰,从而避免了线程安全问题。

需要注意的是,ThreadLocal变量的值只在当前线程中有效,如果线程结束或被回收,ThreadLocal变量的值也会被自动清除。因此,在使用ThreadLocal时,需要注意及时清除线程局部变量的值,以免造成内存泄漏。

ThreadLocal的应用场景比较广泛,例如在Web应用中,可以使用ThreadLocal来存储当前请求的用户信息,以便在整个请求处理过程中都可以访问和修改这些信息。

ThreadLocalMap

ThreadLocalMap是一个定制化的哈希映射,用于存储线程局部变量。每个线程都有一个独立的ThreadLocalMap实例,用于存储该线程的所有ThreadLocal变量。ThreadLocalMap的键是ThreadLocal对象,值是线程局部变量的值。

当我们调用ThreadLocalset()方法时,实际上是将线程局部变量的值存储到当前线程的ThreadLocalMap中。同样,当我们调用ThreadLocalget()方法时,实际上是从当前线程的ThreadLocalMap中获取线程局部变量的值。

弱引用

弱引用(WeakReference)是Java中的一种引用类型,它的特点是在垃圾回收器运行时,无论内存是否充足,都会回收弱引用指向的对象。这种特性使得弱引用在解决内存泄漏问题方面具有优势。

ThreadLocal中,ThreadLocalMap的键使用了弱引用。这样,当ThreadLocal对象不再被其他地方引用时,垃圾回收器会回收ThreadLocal对象,从而避免了内存泄漏。

然而,仅仅使用弱引用并不能完全解决内存泄漏问题。因为ThreadLocalMap的值仍然是强引用,当ThreadLocal对象被回收后,ThreadLocalMap中的值仍然存在,导致内存泄漏。为了解决这个问题,ThreadLocal在调用set()get()remove()方法时,会检查ThreadLocalMap中的无效键,并清除对应的值,从而避免内存泄漏。

总之,ThreadLocal通过结合ThreadLocalMap和弱引用实现了线程局部变量的存储和管理。ThreadLocalMap用于存储每个线程的线程局部变量,而弱引用则用于解决ThreadLocal在使用过程中可能出现的内存泄漏问题。

提问?

Q1: 如果ThreadLocal对象被回收了,那么ThreadLocalMap中对应的key是什么,是null吗?

A1: 如果变为null的话,当ThreadLocalMap中多个ThreadLocal对象都被清除了,那岂不是有多个key为null?这显然不符合Map定义!

因此,在ThreadLocalMap中,键(key)使用的是ThreadLocal对象的弱引用(WeakReference),即ThreadLocal.WeakReference类型。而不是直接使用ThreadLocal对象。当ThreadLocal对象被回收后,ThreadLocalMap中对应的键(key)会变成一个指向null的弱引用,而不是直接变成null

Q2: 弱引用的ThreadLocal对象会不会被误清除,比如当前ThreadLocal对象没有其他引用,随后被清除了。但是,线程后续的运行逻辑会用到ThreadLocal对象,但已经被清除了?

A2: 确实存在这种情况。当ThreadLocal对象没有其他强引用时,它可能会被垃圾回收器回收,即使线程后续的运行逻辑仍然需要使用这个ThreadLocal对象。为了避免这种情况,我们需要确保在使用ThreadLocal时,保持对ThreadLocal对象的强引用。

如下方式可以帮助避免这种问题:

  1. ThreadLocal对象定义为类的静态成员变量。这样可以确保ThreadLocal对象在整个类的生命周期内都有强引用,不会被垃圾回收器回收。
public class MyClass {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
}
  1. 如果ThreadLocal对象需要在方法内部使用,可以将其作为方法的局部变量,并在方法执行过程中保持对ThreadLocal对象的引用。这样可以确保在方法执行过程中,ThreadLocal对象不会被垃圾回收器回收。
public void myMethod() {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    // 使用 threadLocal 的逻辑
}
  1. 如果ThreadLocal对象需要在多个方法之间共享,可以将其作为类的实例成员变量,并在类的生命周期内保持对ThreadLocal对象的引用。
public class MyClass {
    private final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public void method1() {
        // 使用 threadLocal 的逻辑
    }

    public void method2() {
        // 使用 threadLocal 的逻辑
    }
}

总之,为了避免ThreadLocal对象被误清除,我们需要确保在使用ThreadLocal时,保持对ThreadLocal对象的强引用。可以通过将ThreadLocal对象定义为静态成员变量、方法局部变量或类实例成员变量来实现这一目的。