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(预览特性)解决父子线程传值问题。
相关推荐
某空m1 小时前
【Android】View滑动的实现
android
芝麻开门-新起点2 小时前
Android 和 iOS 系统版本及开发适配
android·ios·cocoa
2501_915918412 小时前
iOS描述文件功能解析
android·macos·ios·小程序·uni-app·cocoa·iphone
用户69371750013843 小时前
一文彻底搞懂 Android 依赖配置:implementation vs testImplementation,再也不混淆!
android
TimeFine4 小时前
Android WebView暗夜模式适配
android
studyForMokey4 小时前
【Android Activity】生命周期深入理解
android·kotlin
浅影歌年4 小时前
Android 嵌入h5顶部状态栏空白
android
来来走走7 小时前
kotlin学习 lambda编程
android·学习·kotlin
无知的前端7 小时前
一文精通-Kotlin中双冒号:: 语法使用
android·kotlin
Andy8 小时前
Mysql基础2
android·数据库·mysql