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 小时前
Android 12 - 部分相机横屏显示方案
android
人生游戏牛马NPC1号3 小时前
学习 Flutter (一)
android·学习·flutter
fundroid3 小时前
Swift 进军 Android,Kotlin 该如何应对?
android·ios
前端世界4 小时前
鸿蒙系统安全机制全解:安全启动 + 沙箱 + 动态权限实战落地指南
android·安全·harmonyos
_一条咸鱼_6 小时前
Vulkan入门教程:源码级解析
android·面试·android jetpack
wkj0017 小时前
php 如何通过mysqli操作数据库?
android·数据库·php
kymjs张涛8 小时前
零一开源|前沿技术周报 #7
android·前端·ios
wuwu_q10 小时前
RK3566/RK3568 Android11 修改selinux模式
android·rk3568
_一条咸鱼_11 小时前
Android Runtime内存共享与访问控制原理剖析(71)
android·面试·android jetpack