一、ThreadLocal 核心概念

1. 定义

ThreadLocal 是 Java 提供的线程局部变量工具类,用于在多线程环境下为每个线程维护独立的变量副本,确保线程安全与数据隔离。变量通常声明为 private static 类型,用于关联线程上下文。

2. 核心特性

  • 线程安全:多线程并发访问时,变量副本独立,无需额外同步
  • 数据传递:同一线程内不同组件可共享变量,避免参数传递耦合
  • 线程隔离:各线程变量互不干扰,实现无锁并发

3. 与 synchronized 的区别

对比维度 synchronized ThreadLocal
实现原理 以时间换空间,单变量 + 线程排队访问 以空间换时间,线程私有变量副本
核心目标 多线程共享资源的同步控制 多线程数据隔离,独立访问
并发性能 串行执行,性能开销较大 并行执行,无同步开销
适用场景 多线程需修改共享资源 多线程需独立变量副本

二、ThreadLocal 基本使用

1. 核心方法

方法声明 功能描述
ThreadLocal<T>() 构造 ThreadLocal 实例
void set(T value) 设置当前线程的变量副本
T get() 获取当前线程的变量副本
void remove() 移除当前线程的变量副本
protected T initialValue() 返回变量初始值(默认 null,可重写)

2. 经典使用案例

public class ThreadLocalDemo {
    // 声明 ThreadLocal 变量(通常 static)
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
        // 重写初始值方法(可选)
        @Override
        protected String initialValue() {
            return "默认值";
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                // 设置当前线程的变量值
                threadLocal.set(Thread.currentThread().getName() + "的数据");
                // 获取当前线程的变量值
                System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
                // 移除变量(避免内存泄漏)
                threadLocal.remove();
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

输出结果(线程隔离,数据独立):

线程0--->线程0的数据
线程1--->线程1的数据
线程2--->线程2的数据
线程3--->线程3的数据
线程4--->线程4的数据

三、ThreadLocal 内部结构

1. 设计演进

  • 早期设计:每个 ThreadLocal 维护一个 Map,key 为线程,value 为变量副本(缺点:Thread 数量多时 Map 条目过多)
  • JDK8 优化设计:每个 Thread 维护一个 ThreadLocalMap,key 为 ThreadLocal 实例,value 为变量副本

2. 核心结构

3. 设计优势

  1. 减少 Map 条目数量(由 ThreadLocal 数量决定,通常少于 Thread 数量)
  2. 线程销毁时,ThreadLocalMap 随之销毁,减少内存占用

四、核心方法源码解析

1. set () 方法

public void set(T value) {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();
    // 2. 获取线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 3. 存在则设置键值对(key:当前ThreadLocal实例)
        map.set(this, value);
    } else {
        // 4. 不存在则创建Map并初始化
        createMap(t, value);
    }
}

// 获取线程的 ThreadLocalMap(Thread类的 threadLocals 属性)
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 创建 ThreadLocalMap 并添加第一个条目
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

2. get () 方法

public T get() {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();
    // 2. 获取线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 3. 查找当前ThreadLocal对应的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 4. 存在则返回value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 5. 不存在则初始化(调用initialValue())
    return setInitialValue();
}

// 初始化方法
private T setInitialValue() {
    T value = initialValue(); // 默认返回null,可重写
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    return value;
}

3. remove () 方法

public void remove() {
    // 1. 获取当前线程的 ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        // 2. 移除当前ThreadLocal对应的条目
        m.remove(this);
    }
}

4. initialValue () 方法

protected T initialValue() {
    // 默认返回null,子类可重写以设置非null初始值
    return null;
}

五、ThreadLocalMap 深度解析

1. 基本结构

  • 本质:ThreadLocal 的内部类,独立实现 Map 功能(未实现 Map 接口)
  • 核心属性
    private static final int INITIAL_CAPACITY = 16; // 初始容量(2的幂)
    private Entry[] table; // 存储条目数组(长度为2的幂)
    private int size = 0; // 条目数量
    private int threshold; // 扩容阈值(默认是容量的2/3)
    

2. Entry 结构

static class Entry extends WeakReference<ThreadLocal<?>> {
    // 变量副本(强引用)
    Object value;

    // key为ThreadLocal实例(弱引用)
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
  • key 设计:弱引用(WeakReference),目的是解绑 ThreadLocal 与线程的生命周期
  • value 设计:强引用,需手动移除或线程销毁才能回收

3. 内存泄漏问题

(1)核心概念
  • 内存泄漏:动态分配的堆内存未释放或无法释放,导致内存浪费
  • 内存溢出:内存泄漏堆积的最终结果,无足够内存供程序使用
(2)泄漏原因分析

(3)关键前提(两者缺一不可)
  1. 未手动调用 remove() 方法移除 Entry
  2. 线程未销毁(如线程池复用线程)
(4)弱引用的作用
  • 若 key 为强引用:ThreadLocal 实例无法被回收,Entry 完全无法清理
  • 若 key 为弱引用:ThreadLocal 实例可被回收(key 变为 null),后续调用 set/get/remove 时会清理 value 为 null 的 Entry
(5)解决方案
  1. 强制要求:使用完 ThreadLocal 后调用 remove() 方法(最关键)
  2. 避免线程长时间存活(如合理配置线程池超时时间)

4. 哈希冲突解决

  • 解决方式:线性探测法(ThreadLocalMap 不支持链地址法)

  • 探测逻辑

    1. 计算 key 的索引:i = key.threadLocalHashCode & (len-1)
    2. 若索引 i 已有 Entry 且 key 不匹配,则 i = (i+1) % len
    3. 循环探测直到找到空位置或匹配 key
  • 哈希码设计

    // 原子类保证线程安全的哈希码生成
    private static AtomicInteger nextHashCode = new AtomicInteger();
    // 黄金分割数(斐波那契数列),保证哈希码均匀分布
    private static final int HASH_INCREMENT = 0x61c88647;
    // 每个ThreadLocal实例的哈希码
    private final int threadLocalHashCode = nextHashCode.getAndAdd(HASH_INCREMENT);
    

六、ThreadLocal 典型应用场景

1. Spring 事务管理

  • 原理:Spring 将数据库连接 Connection 存入 ThreadLocal,同一事务的所有操作共享该连接
  • 优势:跨 DAO/Service 层传递连接,无需显式参数传递

2. 其他场景

  • 存储用户会话信息(如登录用户 ID)
  • 日志追踪(存储请求 ID,贯穿整个调用链)
  • 避免方法参数传递(如多层嵌套调用的公共变量)
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐