故事:公司的 "私人储物柜" 系统(ThreadLocalMap)

假设你在一家大公司工作,公司有个特殊规定:每个员工可以申请 "私人储物柜",用来存放自己的私人物品(比如钥匙、笔记本),而且只有自己能看到和使用自己的柜子

  • 公司就像我们的程序

  • 每个员工就像一个线程(Thread)

  • 储物柜系统就像ThreadLocal

  • 员工存的物品就是我们要隔离的变量

比如,员工 A 存了一本笔记本,员工 B 存了一把钥匙,两人互不干扰 ------ 就算用了同一个 "储物柜系统"(同一个 ThreadLocal 实例),也看不到对方的东西。

为什么需要这样的系统?

在多线程场景中,如果多个线程共享一个变量,很容易出现 "混乱"(线程安全问题)。比如两个线程同时修改一个变量,可能导致结果错误。

ThreadLocal 的作用就是:让每个线程拥有变量的 "私人副本" ,线程间互不干扰,从根本上避免了共享变量的冲突。

代码体验:ThreadLocal 的基本使用

先看一段简单代码,感受下 ThreadLocal 的效果:

java 复制代码
public class ThreadLocalDemo {
    // 创建一个ThreadLocal实例(相当于"储物柜系统")
    private static ThreadLocal<String> userThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 线程1:存"张三"
        new Thread(() -> {
            userThreadLocal.set("张三");
            System.out.println("线程1的用户:" + userThreadLocal.get()); // 输出:张三
        }).start();

        // 线程2:存"李四"
        new Thread(() -> {
            userThreadLocal.set("李四");
            System.out.println("线程2的用户:" + userThreadLocal.get()); // 输出:李四
        }).start();
    }
}

运行后会发现:两个线程用同一个userThreadLocal,但各自存的值互不影响。这就是 "私人储物柜" 的效果。

源码拆解:ThreadLocal 是如何实现的?

核心原理可以总结为一句话:每个线程(Thread)内部都维护了一个 "储物柜列表"(ThreadLocalMap),ThreadLocal 实例只是这个列表的 "钥匙"

我们逐步看源码:

1. 线程的 "储物柜列表":Thread 类中的 threadLocals

每个 Thread 对象里,都有一个特殊的变量threadLocals,它的类型是ThreadLocal.ThreadLocalMap(可以理解为 "储物柜列表"):

java 复制代码
public class Thread implements Runnable {
    // 线程的"储物柜列表",由ThreadLocal维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

这个threadLocals默认是null,就像员工刚入职时,还没有分配储物柜。

2. ThreadLocal 的 set () 方法:存东西到自己的柜子

当我们调用threadLocal.set(value)时,到底发生了什么?

ThreadLocalset方法源码:

java 复制代码
public void set(T value) {
    // 1. 获取当前线程(相当于"当前员工")
    Thread t = Thread.currentThread();
    // 2. 拿到当前线程的"储物柜列表"(threadLocals)
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 3. 如果列表存在,用当前ThreadLocal实例作为"钥匙",存值
        map.set(this, value);
    } else {
        // 4. 如果列表不存在,为线程创建一个新的"储物柜列表"
        createMap(t, value);
    }
}

// 获取线程的threadLocals(储物柜列表)
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 为线程创建新的储物柜列表,并存入第一个值
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

流程拆解:

  • 先找到 "当前员工"(当前线程)

  • 查看他有没有 "储物柜列表"(threadLocals)

    • 有:用当前 ThreadLocal 作为 "钥匙",把值存到对应的柜子里
    • 没有:先给员工分配一个新的 "储物柜列表",再存值

3. ThreadLocal 的 get () 方法:取自己柜子里的东西

调用threadLocal.get()时,流程类似:

java 复制代码
public T get() {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();
    // 2. 拿到线程的"储物柜列表"
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 3. 用当前ThreadLocal作为钥匙,找对应的柜子
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 4. 找到就返回值
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 5. 如果没找到,返回默认值(一般是null)
    return setInitialValue();
}

简单说:用当前 ThreadLocal 当钥匙,去当前线程的 "储物柜列表" 里找对应的值。

4. 核心:ThreadLocalMap------ 线程的 "储物柜列表"

ThreadLocalMap是 ThreadLocal 的内部类,本质是一个自定义的哈希表(类似 HashMap,但更简单),用来存储 "钥匙 - 值" 对:

  • 钥匙:ThreadLocal 实例(每个 ThreadLocal 对应一个唯一的 key)

  • 值:我们要存的变量(线程私有副本)

ThreadLocalMap里的 "柜子" 是Entry对象:

java 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    // 我们存的变量值
    Object value;

    // key是ThreadLocal实例,用弱引用包装
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

这里有个关键:Entry的 key(ThreadLocal 实例)是弱引用WeakReference)。这是为了避免内存泄漏:当 ThreadLocal 实例不再被使用时(比如被回收),即使线程还在运行,这个 key 也会被自动清理。

总结:ThreadLocal 的实现逻辑

  1. 每个线程(Thread)都有一个专属的 "储物柜列表"(ThreadLocalMap)
  2. ThreadLocal 实例相当于 "钥匙",用来在列表中定位对应的 "柜子"(Entry)
  3. 调用set(value)时:用当前 ThreadLocal 当钥匙,把值存入当前线程的列表中
  4. 调用get()时:用当前 ThreadLocal 当钥匙,从当前线程的列表中取对应的值
  5. 因为每个线程只能访问自己的列表,所以变量自然就线程隔离了

额外注意:内存泄漏问题

虽然Entry的 key 是弱引用,但 value 是强引用。如果线程长期运行(比如线程池里的核心线程),且忘了调用remove(),那么即使 key 被回收,value 也会一直占着内存,造成泄漏。

所以使用 ThreadLocal 后,最好在合适的时机(比如线程执行结束前)调用remove()清理:

java 复制代码
userThreadLocal.remove(); // 清空当前线程中该ThreadLocal对应的值

用一句话总结:ThreadLocal 通过让每个线程维护自己的变量副本,实现了线程间的变量隔离,而这个 "维护" 的核心就是线程内部的 ThreadLocalMap 和 ThreadLocal 作为钥匙的设计

相关推荐
CYRUS_STUDIO2 小时前
如何防止 so 文件被轻松逆向?精准控制符号导出 + JNI 动态注册
android·c++·安全
ling__i2 小时前
java day18
java·开发语言
非ban必选3 小时前
netty-scoket.io路径配置
java·服务器·前端
yinmaisoft3 小时前
当低代码遇上AI,有趣,实在有趣
android·人工智能·低代码·开发工具·rxjava
渣哥3 小时前
我和Java 8 Stream相爱相杀的那些年
java
爱吃烤鸡翅的酸菜鱼3 小时前
【Spring】原理解析:Spring Boot 自动配置
java·spring boot
小白兔3533 小时前
一文讲通Unicode规范、UTF-8与UTF-16编码及在Java中的验证
java
如此风景3 小时前
Compose Modifier 修饰符介绍
android
纽马约3 小时前
Android BaseQuickAdapter的使用
android