基本概念和作用

在多线程编程中,线程隔离是一个常见的需求,ThreadLocal 就是为了解决这一问题而生。简而言之,ThreadLocal 是一个能够让变量在线程内部保持隔离的机制。每个线程都拥有自己的 ThreadLocal 对象副本,一个线程内的更新不会影响到其他线程。这种特性特别适合实现线程安全的“线程范围内”的数据存储。

ThreadLocal与线程同步机制的区别

与使用 synchronizedLock 等线程同步机制确保变量在多线程之间安全访问的方式不同,ThreadLocal 提供了一种无锁的线程隔离机制。通过为每个线程提供独立的变量副本,从而消除了线程同步的需求,也就消除了性能上的考虑,因为不会存在线程竞争问题。

ThreadLocal的典型应用场景

  • 用户身份信息存储,确保每个线程的用户信息不会相互干扰。
  • 数据库连接管理,通过 ThreadLocal 确保每个线程拥有自己独立的数据库连接。
  • 在框架开发中如 Spring 和 Hibernate 大量使用了 ThreadLocal 来实现线程安全。

实现原理

ThreadLocalMap和Entry

ThreadLocalMap 是 ThreadLocal 的一个内部类,ThreadLocalMap 使用了一种特殊的 Entry 类型来存储键值对。这个 Entry 类型使用了弱引用(WeakReference)来引用 key,即 ThreadLocal 对象。而 value 则存储了存放在 ThreadLocal 中的实际对象。

Thread

public class Thread implements Runnable {

    ...

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    ...
}

从 Thread 源码中可以发现,Thread 类中存在 ThreadLocalMap 类型的成员变量,这样一个Map作为存储结构, Entry 的 key 是 ThreadLocal 对象的弱引用, value 是在该线程中ThreadLocal要存储的值。 当调用 ThreadLocal 类的 get() 方法时,会在当前线程的成员变量 ThreadLocalMap 中查询 key ( ThreadLocal 对象弱引用)对应的 value 值。

弱引用好处与内存泄漏问题

假设在业务代码中使用完 ThreadLocal,threadLocalRef 被回收了,由于 ThreadLocalMap 只持有 ThreadLocal 的弱引用,没有任何强引用指向 threadlocal 实例,所以 threadlocal 就可以顺利被 GC 回收,此时 Entry 中的 key = null。 但在没有手动删除这个 Entry 以及 CurrentThread 依然运行的前提下,仍存在有强引用链 threadRef -> currentThread -> threadLocalMap -> entry -> value,因此value 不会被回收,而这块 value 永远不会被访问到了,从而导致 value 内存泄漏的问题。 要避免这个问题,就必须保证当不再需要存放在 ThreadLocal 中的对象时,显式调用 ThreadLocal 的 remove() 方法。

使用方法

创建ThreadLocal实例

private static final ThreadLocal<MyObject> threadLocalInstance = new ThreadLocal<>();

ThreadLocal 实例通常使用 static final 修饰是为了确保每个线程都可以访问到相同的 ThreadLocal 实例。由于 ThreadLocal 是线程本地变量,每个线程都需要访问相同的实例,因此使用 static final 修饰确保所有线程都可以共享相同的实例。

存取ThreadLocal中的值

// 存入值
threadLocalInstance.set(new MyObject());

// 取出值
MyObject myObject = threadLocalInstance.get();

// 移除值
threadLocalInstance.remove();

代码示例

public class ThreadLocalExample {
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        new Thread(() -> {
            threadLocal.set(1);
            printThreadLocalValue();
            threadLocal.remove();
        }).start();

        new Thread(() -> {
            threadLocal.set(2);
            printThreadLocalValue();
            threadLocal.remove();
        }).start();
    }
    
    private static void printThreadLocalValue() {
        System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
    }
}

总结

使用注意事项

  • 始终记得在不需要使用 ThreadLocal 存储的对象时,调用 remove() 方法来移除。
  • 考虑到垃圾回收和内存泄漏的问题,合理使用 ThreadLocal。

优缺点

优点:

  • 提供线程隔离以确保线程安全。
  • 提升性能,因为不需要额外的同步措施。

缺点:

  • 不当使用可能会导致内存泄漏。
  • 可能会使代码的可读性和可维护性降低。

ThreadLocal 是并发编程中的一个强大工具,如果能够合理使用,并注意避免内存泄漏的问题,它将成为你的多线程程序中的利器。