ThreadLocal详解

什么是 ThreadLocal?​

ThreadLocal 是 Java 中的一个工具类,用于为每个线程提供独立的变量副本,使得每个线程可以独立操作自己的变量,避免多线程环境下的数据竞争问题。它的核心思想是​​线程封闭​​(Thread Confinement),即通过将变量限制在线程内部使用,实现线程安全。

ThreadLocal 的核心机制​

存储结构​​

每个线程(Thread 对象)内部维护了一个 ThreadLocalMap,键(Key)是 ThreadLocal 实例,值(Value)是该线程的局部变量副本。

java 复制代码
// 简化的 ThreadLocal 结构
public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        Entry e = map.getEntry(this);
        return (T)e.value;
    }
}

ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

数据隔离​​

不同线程访问同一个 ThreadLocal 时,实际上操作的是各自线程的 ThreadLocalMap 中的不同数据,天然线程安全。

ThreadLocal 的 API​

void set(T value)

设置当前线程的变量副本

java 复制代码
 public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
    }

ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化

T get()

获取当前线程的变量副本

java 复制代码
    public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据不为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
    }

void remove()

移除当前线程的变量副本

java 复制代码
 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

initialValue()

初始化变量(需重写,默认返回null)

java 复制代码
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

ThreadLocal 的典型使用场景​

全局变量在线程间隔离​​

例如用户会话(Session)、数据库连接、事务上下文等。

​​线程安全的工具类​​

如 SimpleDateFormat 是非线程安全的,通过 ThreadLocal 为每个线程分配独立实例:

java 复制代码
private static final ThreadLocal<SimpleDateFormat> dateFormat =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String formatDate(Date date) {
    return dateFormat.get().format(date);
}

​ 跨方法传递隐式参数​​

避免在方法间层层传递参数,例如在日志框架中保存请求ID。

ThreadLocal 的内存泄漏问题​

原因​​

ThreadLocalMap内部维护了一个Entry[] table来存储键值对的映射关系,内存泄漏和Entry类有非常大的关系,下面是Entry的源码:

java 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value; // 强引用
    Entry(ThreadLocal<?> k, Object v) {
        super(k); // Key 是弱引用
        value = v;
    }
}

ThreadLocalMap中的Entry的key是弱引用指向ThreadLocal实例,而value是强引用。(弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收)。

解决方案

自动清理机制(部分场景触发)​

当调用 ThreadLocal 的 ​​get()、set() 或 remove()​​ 方法时,ThreadLocalMap 会尝试清理 Key 已被回收的 Entry(即 Key 为 null 的 Entry)。但这种清理是 ​​非彻底​​ 的:

​​触发条件​​:操作过程中发现 Key 为 null 的 Entry。

​​清理范围​​:仅清理当前操作路径上的 Entry,不会全量扫描。(从当前操作的哈希槽位(Hash Slot)开始,向后线性探测遍历​​,直到遇到 Entry 为 null 的槽位为止。在此路径上遇到的 ​​所有 Key 为 null 的 Entry​​ 会被直接清理,但其他位置的无效 Entry 可能仍残留。)

示例​​:

java 复制代码
threadLocal.set(123); // 触发部分清理
threadLocal.get();    // 触发部分清理

显式调用 remove()​​:

使用完 ThreadLocal 后,调用 remove() 清理 Entry。

​​使用 try-finally 块​​:

java 复制代码
try {
    threadLocal.set(value);
    // ... 业务逻辑
} finally {
    threadLocal.remove();
}

InheritableThreadLocal

默认情况下,子线程无法继承父线程的 ThreadLocal 变量。使用 InheritableThreadLocal 解决此问题:

java 复制代码
private static final InheritableThreadLocal<String> inheritableThreadLocal =
    new InheritableThreadLocal<>();

public static void main(String[] args) {
    inheritableThreadLocal.set("Parent Value");
    new Thread(() -> {
        System.out.println(inheritableThreadLocal.get()); // 输出: Parent Value
    }).start();
}
相关推荐
zhangxueyi4 分钟前
Java实现基数排序算法
java·数据结构·算法
Another Iso2 小时前
同时启动俩个tomcat压缩版
java·tomcat
华纳云IDC服务商2 小时前
华纳云:centos如何实现JSP页面的动态加载
java·linux·centos
碎梦归途2 小时前
23种设计模式-行为型模式之访问者模式(Java版本)
java·开发语言·jvm·设计模式·软考·软件设计师·行为型模式
Demons_kirit3 小时前
LeetCode LCP40 心算挑战题解
java·数据结构·算法·leetcode·职场和发展
Jiaberrr3 小时前
uniapp 实现低功耗蓝牙连接并读写数据实战指南
java·前端·javascript·vue.js·struts·uni-app
家乡的落日3 小时前
一、I/O的相关概念
java
码熔burning3 小时前
【MQ篇】RabbitMQ之死信交换机!
java·分布式·rabbitmq·mq
黄雪超4 小时前
JVM——Java的基本类型的实现
java·开发语言·jvm
工业互联网专业5 小时前
基于web的可追溯果蔬生产过程的管理系统
java·vue.js·spring boot·毕业设计·源码·课程设计·可追溯果蔬生产过程的管理系统