ThreadLocal线程本地存储

ThreadLocal线程本地存储详解

在并发编程中,处理共享数据时通常需要同步机制(如synchronizedLock)来保证线程安全。但 ThreadLocal 提供了另一种思路:让共享数据不再共享,通过为每个线程创建独立的变量副本,实现线程间的数据隔离。

ThreadLocal 核心原理

基本概念

ThreadLocal(线程本地变量)为每个线程维护一个独立的变量副本,线程对副本的修改不会影响其他线程。其核心思想是:

  • 空间换时间:通过为每个线程分配独立内存空间,避免线程间的同步开销。
  • 数据隔离:每个线程操作自己的副本,消除竞争条件。

实现结构

ThreadLocal 的实现依赖于Thread类和ThreadLocalMap

  • Thread 类:每个线程持有两个ThreadLocalMap对象:

    ThreadLocal.ThreadLocalMap threadLocals = null;       // 线程本地变量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;  // 可继承的本地变量
  • ThreadLocalMap:ThreadLocal 的静态内部类,本质是一个哈希表,key 为 ThreadLocal 对象,value 为线程的变量副本

ThreadLocal 核心方法

ThreadLocal 通过以下方法操作线程的变量副本:

get():获取当前线程的变量副本

public T get() {
    Thread t = Thread.currentThread();       // 获取当前线程
    ThreadLocalMap map = getMap(t);          // 获取线程的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);  // 以当前ThreadLocal为key查询
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();  // 若map为空,初始化并返回初始值
}

set(T value):设置当前线程的变量副本

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);  // 以当前ThreadLocal为key存储值
    else
        createMap(t, value);   // 若map为空,初始化map
}

initialValue():初始化变量副本(默认 null)

需重写此方法设置初始值,例如:

private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
        return 0;  // 初始值设为0
    }
};

remove():移除当前线程的变量副本

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);  // 移除当前ThreadLocal对应的value
}

ThreadLocalMap 详解

ThreadLocalMap是 ThreadLocal 的核心存储结构,采用开地址法解决哈希冲突,内部由Entry数组实现。

Entry 结构

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;  // 线程的变量副本
    Entry(ThreadLocal<?> k, Object v) {
        super(k);  // key是弱引用(ThreadLocal对象)
        value = v;
    }
}
  • key 为弱引用:当 ThreadLocal 对象无强引用时,会被 GC 回收,避免内存泄漏。
  • value 为强引用:需手动调用remove()释放,否则可能内存泄漏。

核心方法

set(ThreadLocal<?> key, Object value):存储键值对
  • 计算 key 的哈希值确定数组下标。
  • 若发生哈希冲突,采用线性探测法(依次检查下一个位置)寻找空闲槽位。
  • 若遇到 key 为 null 的 Entry(已被 GC 回收),则替换该位置的无效数据。
getEntry(ThreadLocal<?> key):查询值
  • 先通过哈希值定位下标,若命中直接返回。
  • 若未命中,通过线性探测继续查找,同时清理 key 为 null 的无效 Entry。
remove(ThreadLocal<?> key):移除值
  • 找到 key 对应的 Entry 后,清除其 value 并标记为 null,触发 GC 回收。

线程继承性:InheritableThreadLocal

ThreadLocal的变量无法被子线程继承,而InheritableThreadLocal(ThreadLocal 的子类)解决了这一问题,允许子线程访问父线程的本地变量。

实现原理

  • 重写getMap()createMap(),使用线程的inheritableThreadLocals而非threadLocals

  • 子线程创建时,会复制父线程的inheritableThreadLocals到自身:

    // Thread类的init方法
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = 
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

应用场景

  • Web 开发中,用于在子线程中获取父线程的请求上下文(如RequestContextHolder)。

内存泄漏问题

ThreadLocal内存泄漏

泄漏原因

  • Entry 的 key 是弱引用,ThreadLocal 对象被回收后,key 变为 null。
  • 但 value 是强引用,若线程未销毁(如线程池复用),value 会一直占用内存,导致泄漏。

解决方案

  • 手动调用remove():使用完 ThreadLocal 后,显式调用remove()清除 value。
  • JDK 的防护机制get()set()remove()方法会自动清理 key 为 null 的 Entry。

示例代码与运行结果

示例代码

public class ThreadLocalVariableHolder {
    private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 初始值为0
        }
    };

    public static void increment() {
        myThreadLocal.set(myThreadLocal.get() + 1);
    }

    public static int get() {
        return myThreadLocal.get();
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.execute(new MyThread());
        }
        executorService.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            ThreadLocalVariableHolder.increment();
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalVariableHolder.get());
            Thread.yield();
        }
    }
}

运行结果

pool-1-thread-1:1
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-1:2
pool-1-thread-2:2
pool-1-thread-3:2
pool-1-thread-1:3
pool-1-thread-2:3
pool-1-thread-3:3

每个线程的变量独立递增,互不干扰,验证了 ThreadLocal 的隔离性。