ThreadLocal 详解

一、核心概念

  1. 线程局部变量:每个线程拥有独立的变量副本,互不干扰。
  2. 线程封闭:通过空间换时间避免线程同步。
  3. 弱引用机制 :Key 使用弱引用防止内存泄漏(但需配合 remove() 清理)。

二、源码解析(JDK 11)

1. 存储结构
kotlin 复制代码
// Thread 类中的存储
class Thread {
    ThreadLocal.ThreadLocalMap threadLocals; // 存储当前线程的ThreadLocal数据
}
2. ThreadLocalMap 核心结构
scala 复制代码
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value; // 实际存储的值(强引用)
        Entry(ThreadLocal<?> k, Object v) {
            super(k); // Key 弱引用指向ThreadLocal实例
            value = v;
        }
    }
    
    private Entry[] table; // 哈希表
    private static final int INITIAL_CAPACITY = 16;
}
3. 关键方法
java 复制代码
public class ThreadLocal<T> {
    // 获取值
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            Entry e = map.getEntry(this); // 以当前ThreadLocal为Key查询
            if (e != null) return (T)e.value;
        }
        return setInitialValue(); // 初始化
    }
    
    // 设置值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) map.set(this, value);
        else createMap(t, value); // 首次使用创建Map
    }
    
    // 删除值(防内存泄漏)
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null) m.remove(this);
    }
}
4. 哈希冲突解决
  • 开放地址法 :线性探测(nextIndex()/prevIndex())。
  • 再哈希HASH_INCREMENT = 0x61c88647(斐波那契散列)。

三、内存泄漏问题

  1. 根本原因

    • Key(ThreadLocal)是弱引用,GC 可回收。
    • Value 是强引用,线程未结束则一直存在。
  2. 解决方案

    • 使用后必须调用 remove() 清理。
    • 避免线程池中长时间存活线程积累数据。

四、使用场景

  1. 上下文传递

    csharp 复制代码
    public class UserContext {
        private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
        
        public static void set(User user) {
            currentUser.set(user);
        }
        
        public static User get() {
            return currentUser.get();
        }
        
        public static void clear() {
            currentUser.remove(); // 请求结束时清理
        }
    }
    • 在 Web 拦截器中设置用户信息,业务层直接获取。
  2. 线程安全工具类

    vbnet 复制代码
    public class DateUtils {
        private static final ThreadLocal<SimpleDateFormat> dateFormat = 
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
        
        public static String format(Date date) {
            return dateFormat.get().format(date);
        }
    }
    • 解决 SimpleDateFormat 非线程安全问题。

五、继承性问题

  • InheritableThreadLocal:子线程继承父线程变量。
  • 局限性 :线程池中线程复用会导致继承失效(需用 TransmittableThreadLocal 等方案)。

六、实践

  1. 声明为 static final 减少实例数量。

  2. 必须finally 块中调用 remove()

    csharp 复制代码
    try {
        threadLocal.set(data);
        // ... 业务逻辑
    } finally {
        threadLocal.remove(); // 强制清理
    }
  3. 避免存储大对象(每个线程独立拷贝)。

七、与其它技术对比

技术 特点 适用场景
ThreadLocal 线程隔离,无锁 上下文传递、线程安全工具类
synchronized 线程阻塞,保证原子性 共享资源竞争
volatile 可见性,不保证原子性 状态标志、双重检查锁
AtomicInteger CAS 无锁,保证原子性 计数器、累加器

注意:在分布式系统中,ThreadLocal 不能跨节点传递(需结合 RPC 上下文透传)。

八、总结

  • 核心价值:高效线程封闭,避免同步开销。
  • 典型应用:上下文管理、线程不安全工具类改造。
  • 致命陷阱 :内存泄漏(必须配合 remove())。
  • 替代方案 :Java 13 引入 Scoped Values(预览特性)解决父子线程传值问题。
相关推荐
逐光老顽童1 天前
Java 与 Kotlin 混合开发避坑指南:30 个真实案例实录
android·kotlin
爱勇宝2 天前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
Yeyu2 天前
刷新一帧的艺术:invalidate / postInvalidate / postInvalidateOnAnimation全解析
android
潘潘潘2 天前
Android OTA 升级原理和流程介绍
android
plainGeekDev2 天前
null 判断 → Kotlin 可空类型
android·java·kotlin
plainGeekDev2 天前
getter/setter → Kotlin 属性
android·java·kotlin
YXL1111YXL2 天前
Handler 消息回收与协程异步执行的时序陷阱
android
恋猫de小郭2 天前
KMP / CMP 鸿蒙版本 Beta 发布,他有什么特别之处?
android·前端·flutter
三少爷的鞋2 天前
Android 协程并发控制:别动线程池,控制好并发语义就够了
android
weiggle3 天前
第七篇:状态提升与单向数据流——架构设计的核心
android