在多线程编程中,我们常面临“共享变量并发访问”的问题。synchronizedLock 通过“互斥”保证线程安全,但有时我们需要的是“线程私有”的变量——每个线程操作自己的副本,无需同步。ThreadLocal 正是为这种场景设计的工具。

本文将从 ThreadLocal 的核心特性出发,结合源码解析其实现原理,最后探讨实际应用场景与避坑指南。

一、ThreadLocal 核心特性

ThreadLocal(线程本地变量)的核心价值是让变量在每个线程中拥有独立副本,从而避免多线程共享带来的并发问题。其核心特性可概括为:

  1. 线程隔离性:每个线程只能访问自己的变量副本,对其他线程不可见。
  2. 以线程为key存储:变量副本实际存储在线程内部,ThreadLocal 仅作为“访问入口”。
  3. 简单的API:通过 set() 存值、get() 取值、remove() 清理,无需关注底层线程交互。
  4. 初始值机制:支持通过 initialValue() 为每个线程的副本设置初始值。

二、源码解析:特性如何通过代码实现?

ThreadLocal 的源码并不复杂,核心是**“线程持有变量副本,ThreadLocal 负责管理访问”**的设计。我们从 JDK 8 的源码出发,逐个验证其特性。

特性1:线程隔离性——每个线程有独立副本

现象:
// 创建 ThreadLocal 实例
ThreadLocal<String> tl = new ThreadLocal<>();

// 线程1设置值
new Thread(() -> {
    tl.set("线程1的副本");
    System.out.println(Thread.currentThread().getName() + ": " + tl.get()); // 线程1的副本
}).start();

// 线程2设置值
new Thread(() -> {
    tl.set("线程2的副本");
    System.out.println(Thread.currentThread().getName() + ": " + tl.get()); // 线程2的副本
}).start();

两个线程的 set()get() 操作互不干扰,证明变量副本是线程隔离的。

源码证明:

核心设计:变量副本并非存储在 ThreadLocal 中,而是存储在线程(Thread)自身的成员变量中。

  1. Thread 类的关键成员
    每个 Thread 实例都有一个 threadLocals 变量,类型为 ThreadLocal.ThreadLocalMapThreadLocal 的静态内部类,本质是一个哈希表):

    public class Thread implements Runnable {
        // 线程私有变量的存储容器,由 ThreadLocal 管理
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    

    这个 threadLocals 就是当前线程存储所有 ThreadLocal 变量副本的地方。

  2. set() 方法如何保证隔离
    当调用 tl.set(value) 时,实际是往当前线程的 threadLocals 中存值

    public class ThreadLocal<T> {
        public void set(T value) {
            // 1. 获取当前线程
            Thread t = Thread.currentThread();
            // 2. 获取当前线程的 threadLocals 容器
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 3. 若容器存在,以当前 ThreadLocal 实例为 key,存入 value
                map.set(this, value);
            } else {
                // 4. 若容器不存在,为线程创建容器并存入值
                createMap(t, value);
            }
        }
        
        // 获取线程的 threadLocals 容器
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        
        // 为线程创建 threadLocals 容器
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    

    关键逻辑:value 最终存储在 当前线程的 threadLocals 中,且以 ThreadLocal 实例为 key——因此每个线程的 value 完全隔离。

  3. get() 方法如何获取自己的副本
    同理,get() 是从当前线程的 threadLocals 中取值

    public T get() {
        // 1. 获取当前线程
        Thread t = Thread.currentThread();
        // 2. 获取当前线程的 threadLocals 容器
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 3. 以当前 ThreadLocal 实例为 key,获取 value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 4. 若容器为空或无值,返回初始值(后面会讲)
        return setInitialValue();
    }
    

结论:通过将变量副本存储在 Thread 实例的 threadLocals 中,实现了“每个线程只能访问自己的副本”,这是线程隔离性的根本原因。

特性2:以线程为key存储——ThreadLocalMap 的作用

ThreadLocal 本身不存储数据,而是通过 ThreadLocalMap 实现“线程到变量副本”的映射。ThreadLocalMapThreadLocal 的静态内部类,本质是一个定制化的哈希表,专为线程本地变量设计。

源码解析 ThreadLocalMap:
  1. 数据结构

    static class ThreadLocalMap {
        // 哈希表的节点,key 是 ThreadLocal 实例,value 是变量副本
        static class Entry extends WeakReference<ThreadLocal<?>> {
            // 变量副本(强引用)
            Object value;
            
            // key 是弱引用(ThreadLocal 实例),value 是强引用
            Entry(ThreadLocal<?> k, Object v) {
                super(k); // 调用 WeakReference 构造函数,key 为弱引用
                value = v;
            }
        }
        
        // 哈希表数组(Entry[])
        private Entry[] table;
        
        // 其他成员:size(元素数量)、threshold(扩容阈值)等
    }
    

    关键设计:EntrykeyThreadLocal 实例的弱引用WeakReference),而 value强引用(这与内存泄漏有关,后面会讲)。

  2. set() 方法的核心逻辑
    ThreadLocalMap.set(ThreadLocal<?> key, Object value) 负责将键值对存入哈希表:

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        
        // 1. 计算 key 的哈希值(基于 ThreadLocal 的 threadLocalHashCode)
        int i = key.threadLocalHashCode & (len-1);
        
        // 2. 线性探测法解决哈希冲突(与 HashMap 的链地址法不同)
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get(); // 获取弱引用指向的 ThreadLocal
            
            // 2.1 若 key 已存在,直接更新 value
            if (k == key) {
                e.value = value;
                return;
            }
            
            // 2.2 若 key 为 null(已被 GC 回收),替换这个无效 Entry
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        
        // 3. 新增 Entry 到哈希表
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 4. 清理无效 Entry(key 为 null 的),若未清理且达到阈值则扩容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    

    HashMap 不同,ThreadLocalMap线性探测法解决哈希冲突(当索引 i 被占用时,依次尝试 i+1i+2…),这是因为它的 Entry 数量通常较少,线性探测效率更高。

  3. getEntry() 方法的核心逻辑
    根据 ThreadLocal 实例获取对应的 value

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // 若命中直接返回
        if (e != null && e.get() == key)
            return e;
        else
            // 若未命中,线性探测查找(同时清理无效 Entry)
            return getEntryAfterMiss(key, i, e);
    }
    

结论ThreadLocalMap 是线程本地变量的实际存储容器,以 ThreadLocal 实例为 key(弱引用),变量副本为 value,通过线性探测法解决哈希冲突,实现“以线程为单位”的变量存储。

特性3:简单的API——set()、get()、remove() 的协作

ThreadLocal 对外提供的 API 非常简洁,核心方法只有三个:

  1. set(T value):为当前线程设置变量副本(前面已解析)。
  2. get():获取当前线程的变量副本(前面已解析)。
  3. remove():删除当前线程的变量副本:
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null) {
            m.remove(this); // 调用 ThreadLocalMap 的 remove 方法
        }
    }
    
    // ThreadLocalMap 的 remove 方法
    private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        // 线性探测找到对应 Entry 并清除
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                e.clear(); // 清除弱引用
                expungeStaleEntry(i); // 清理无效 Entry
                return;
            }
        }
    }
    

这三个方法的协作流程是:

  • set():当前线程 → threadLocals → 存值(ThreadLocal 为 key)。
  • get():当前线程 → threadLocals → 取值(ThreadLocal 为 key)。
  • remove():当前线程 → threadLocals → 删值(ThreadLocal 为 key)。

结论:API 设计简洁,用户无需关注线程交互,只需通过 ThreadLocal 实例操作“当前线程的副本”。

特性4:初始值机制——initialValue() 的作用

如果线程未调用 set() 却调用了 get()ThreadLocal 会通过 initialValue() 为线程设置一个初始值。

源码解析:
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            return (T)e.value;
        }
    }
    // 若 map 为空或无值,调用 setInitialValue() 返回初始值
    return setInitialValue();
}

private T setInitialValue() {
    // 调用 initialValue() 获取初始值(默认返回 null)
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    // 省略一些与内存泄漏相关的逻辑
    return value;
}

// 初始值方法,默认返回 null,可重写
protected T initialValue() {
    return null;
}

使用示例:通过匿名内部类重写 initialValue() 设置初始值:

ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
        return 0; // 每个线程的初始值都是 0
    }
};

// 线程1获取初始值
new Thread(() -> System.out.println(tl.get())).start(); // 输出 0

结论initialValue() 为线程提供了默认的变量副本,避免了 get() 时返回 null 的情况,简化了初始化逻辑。

三、大局观:ThreadLocal 的设计哲学

ThreadLocal 的核心设计哲学是**“空间换时间”**:通过为每个线程创建变量副本,避免多线程竞争带来的同步开销(如 synchronized 的锁竞争)。

从整体架构看,ThreadLocalThreadThreadLocalMap 三者的关系是:

  • Thread 是“容器的所有者”:每个线程有自己的 ThreadLocalMap
  • ThreadLocalMap 是“数据的存储者”:存储 ThreadLocal 与变量副本的映射。
  • ThreadLocal 是“数据的访问者”:提供 set()/get()/remove() 接口,作为访问线程私有变量的入口。

这种设计将“线程私有数据”的管理责任委托给线程自身,ThreadLocal 仅作为“钥匙”,既保证了隔离性,又简化了用户 API。

关于弱引用

在 Java 中,引用类型决定了对象的生命周期与垃圾回收(GC)行为。弱引用(WeakReference)是四大引用类型(强引用、软引用、弱引用、虚引用)之一,其核心特性是 “只要发生 GC,无论内存是否充足,被弱引用关联的对象都会被回收”

强引用:从不主动回收(OOM 也不回收) 普通对象引用(如 Object o = new Object())
弱引用:只要发生 GC,无论内存是否充足都会回收 缓存场景(如 ThreadLocal、WeakHashMap)

ThreadLocal 如何清除 key 为 null 的 Entry(数组元素)

ThreadLocal 中,key null 的情况通常是因为 ThreadLocal 实例被 GC 回收(弱引用特性)。这些 key 为 null 的 Entry(哈希表元素)如果不及时清理,会导致 value 内存泄漏(value 是强引用,无法被回收)。

ThreadLocalMap 通过多种机制主动清理这些无效 Entry,核心逻辑集中在 探测式清理扩容时清理两类操作中。

key 为 null 的 Entry 产生原因

ThreadLocalMap.Entry keyThreadLocal 实例的弱引用(WeakReference);
当 ThreadLocal 实例没有强引用时(如 tl = null),GC 会回收该实例,导致 Entry 的 key 变为 null
此时 Entry 的 value 仍为强引用,若不清理,会被线程长期持有(尤其线程池核心线程),造成内存泄漏。

清除 key 为 null 的 Entry 的核心机制

ThreadLocalMap 不会主动监听 GC 事件,而是在每次操作哈希表时(如 set、get、remove)顺带清理无效 Entry,主要通过以下方法实现:

  • set() 方法中的清理:replaceStaleEntry()cleanSomeSlots()
    set() 是添加 / 更新键值对的方法,在发生哈希冲突或插入新元素时,会主动清理无效 Entry:
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);

    // 线性探测查找位置
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            // 1. 找到相同 key,更新 value 后直接返回
            e.value = value;
            return;
        }

        if (k == null) {
            // 2. 发现 key 为 null 的无效 Entry,替换它
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 3. 插入新 Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 4. 清理部分无效 Entry,若未清理且达到阈值则扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

关键清理逻辑:
replaceStaleEntry():当找到 keynullEntry 时,会:
向前扫描,清除所有连续的无效 Entry;——向前清理
向后扫描,找到与当前 key 冲突的 Entry 并移动,减少哈希冲突;——向后清理

最终用新的键值对替换当前无效 Entry。

cleanSomeSlots():插入新元素后,会随机扫描哈希表中的部分槽位,清除发现的无效Entry(“启发式清理”),返回是否清理到了元素。

+ getEntry() 方法中的清理:getEntryAfterMiss()
get() 方法查询值时,若初始位置未找到目标 key,会触发线性探测,过程中清理遇到的无效 Entry


private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key) {
        // 1. 命中目标 key,直接返回
        return e;
    } else {
        // 2. 未命中,线性探测并清理无效 Entry
        return getEntryAfterMiss(key, i, e);
    }
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            return e; // 找到目标 key
        }
        if (k == null) {
            // 清理 key 为 null 的无效 Entry
            expungeStaleEntry(i);
        } else {
            i = nextIndex(i, len); // 继续探测下一个位置
        }
        e = tab[i];
    }
    return null;
}

expungeStaleEntry(i):清除索引 i 处的无效 Entry,并重新哈希该位置后续的 Entry(解决因清除导致的哈希冲突),返回下一个无效 Entry 的索引。

  • 扩容时的全面清理:rehash() resize()
    当哈希表元素数量达到阈值(threshold)时,会触发扩容,过程中会全面清理所有无效 Entry

private void rehash() {
    // 1. 先清除所有无效 Entry
    expungeStaleEntries();

    // 2. 若清理后元素数量仍超过阈值的 3/4,则扩容
    if (size >= threshold - threshold / 4)
        resize();
}

// 清除所有无效 Entry
private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null) {
            // 逐个清理 key 为 null 的 Entry
            expungeStaleEntry(j);
        }
    }
}

// 扩容为原来的 2 倍
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    // 迁移有效 Entry 到新数组
    for (int j = 0; j < oldLen; j++) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // 主动释放 value 引用,帮助 GC
            } else {
                // 计算新索引,迁移有效 Entry
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    // 更新阈值和数组
    setThreshold(newLen);
    size = count;
    table = newTab;
}

expungeStaleEntries()
遍历整个哈希表,清除所有 keynullEntry,是最彻底的清理操作。
resize()
扩容时仅迁移有效 Entry(key 不为 null),无效 Entry 的 value 会被主动置为 null,加速 GC 回收。

  • remove() 方法中的针对性清理
    调用 remove() 方法时,会直接删除目标 Entry,并清理可能存在的相邻无效 Entry

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

// ThreadLocalMap 的 remove 方法
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear(); // 清除 key 的弱引用
            expungeStaleEntry(i); // 清理该位置及后续的无效 Entry
            return;
        }
    }
}
清理机制的特点:“被动触发 + 增量清理”

ThreadLocalMap 的清理机制并非主动监控,而是 “被动触发”(在操作哈希表时顺带执行),且以“增量清理” 为主(每次清理部分无效 Entry):

  • 操作即清理
    set、get、remove 等操作都会触发不同程度的清理,避免无效 Entry 累积。

  • 平衡性能与内存
    增量清理(如 cleanSomeSlots 随机扫描)避免了单次全量清理的性能开销,适合高并发场景。

  • 扩容时彻底清理
    通过 rehash() 中的 expungeStaleEntries() 做全量清理,确保扩容后哈希表的有效性。

为什么仍需手动调用 remove()?

尽管 ThreadLocalMap 有自动清理机制,但仍可能存在清理不及时的情况

  • 若线程长期存活(如线程池核心线程),且很少操作 ThreadLocal(缺乏 set/get 触发清理),无效 Entry 可能累积导致内存泄漏。
  • 自动清理无法保证 100% 清除所有无效 Entry(如 cleanSomeSlots 是随机扫描,可能遗漏)。

因此,最佳实践是在使用完 ThreadLocal 后,主动调用 remove() 方法,确保及时清理 value,避免内存泄漏。

总结
ThreadLocal 通过以下机制清除 key 为 null 的 Entry:

  • set() 时通过 replaceStaleEntry 和 cleanSomeSlots 清理冲突位置及部分无效 Entry;
  • get() 时通过 getEntryAfterMiss 在线性探测中清理遇到的无效 Entry;
  • 扩容前通过 expungeStaleEntries 全量清理无效 Entry;
  • remove() 时直接清理目标 Entry 及相邻无效 Entry。

这些机制共同作用,在保证性能的前提下,最大限度减少无效 Entry 导致的内存泄漏。但手动调用 remove() 仍是避免内存泄漏的关键。

四、典型使用场景

ThreadLocal 适用于**“变量需要线程私有,且生命周期与线程相关”**的场景,常见案例包括:

1. 数据库连接管理

在 JDBC 中,每个线程需要独立的数据库连接(Connection),避免多线程共用连接导致的事务混乱:

public class DBUtil {
    // 线程私有连接
    private static ThreadLocal<Connection> connHolder = new ThreadLocal<Connection>() {
        @Override
        protected Connection initialValue() {
            return DriverManager.getConnection("url", "user", "pwd");
        }
    };
    
    // 获取当前线程的连接
    public static Connection getConnection() {
        return connHolder.get();
    }
    
    // 关闭当前线程的连接(并清理 ThreadLocal)
    public static void closeConnection() {
        Connection conn = connHolder.get();
        if (conn != null) {
            try { conn.close(); } catch (SQLException e) { ... }
            connHolder.remove(); // 必须清理
        }
    }
}

2. 线程上下文传递

在多层调用(如 Web 开发的 Controller → Service → DAO)中,需要传递线程相关的上下文信息(如用户 ID、日志追踪 ID),无需每层显式传参:

public class ContextHolder {
    // 存储当前线程的用户 ID
    private static ThreadLocal<Long> userIdHolder = new ThreadLocal<>();
    
    public static void setUserId(Long userId) {
        userIdHolder.set(userId);
    }
    
    public static Long getUserId() {
        return userIdHolder.get();
    }
    
    public static void clear() {
        userIdHolder.remove(); // 请求结束后清理
    }
}

// Controller 层设置
ContextHolder.setUserId(123L);

// Service 层或 DAO 层直接获取
Long userId = ContextHolder.getUserId();

3. 线程不安全工具类的封装

某些工具类(如 SimpleDateFormat)是非线程安全的,通过 ThreadLocal 为每个线程提供独立实例:

public class DateUtil {
    // 每个线程一个 SimpleDateFormat 实例
    private static ThreadLocal<SimpleDateFormat> sdfHolder = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    // 线程安全的格式化方法
    public static String format(Date date) {
        return sdfHolder.get().format(date);
    }
}

五、注意事项与避坑指南

ThreadLocal 看似简单,但使用不当会导致内存泄漏数据污染等问题,必须注意以下几点:

1. 内存泄漏风险:为什么必须调用 remove()?

问题根源:

ThreadLocalMapEntry 中,keyThreadLocal 的弱引用,而 value 是强引用:

  • ThreadLocal 实例被回收(如方法执行结束,引用消失),key 会变成 null(弱引用特性)。
  • value 仍被 Entry 强引用,若线程长时间存活(如线程池的核心线程),value 无法被 GC 回收,导致内存泄漏。
解决方案:

使用完 ThreadLocal 后必须调用 remove(),手动清除 value

try {
    tl.set(value);
    // 业务逻辑
} finally {
    tl.remove(); // 确保清除,避免内存泄漏
}

2. 线程池环境下的数据污染

问题场景:

线程池中的线程会被复用,若上一个任务未清理 ThreadLocal 中的值,下一个任务可能读取到旧值:

ExecutorService pool = Executors.newFixedThreadPool(1); // 线程池只有 1 个线程

// 任务1:设置值但不清理
pool.execute(() -> {
    tl.set("任务1的值");
    System.out.println("任务1: " + tl.get());
});

// 任务2:未设置值,却可能读取到任务1的值
pool.execute(() -> {
    System.out.println("任务2: " + tl.get()); // 可能输出 "任务1的值"
});
解决方案:
  • 无论何种场景,都在 finally 中调用 remove(),确保线程复用前清理数据。
  • 线程池任务中使用 ThreadLocal 时,remove() 是强制要求。

3. 不要误解“线程安全”的含义

ThreadLocal 解决的是**“线程隔离”**,而非“共享变量的线程安全”:

  • 若变量本身是线程私有(如每个线程独立的 Connection),ThreadLocal 能保证安全。
  • 若变量是共享的(如静态变量),ThreadLocal 无法解决线程安全问题(此时仍需 synchronized)。

4. 避免滥用:不是所有变量都需要线程私有

ThreadLocal 会增加内存开销(每个线程一个副本),且过度使用会导致代码难以理解。仅在确实需要“线程私有变量”时使用,不要用它替代合理的参数传递。

六、总结

ThreadLocal 通过“线程持有变量副本,自身作为访问入口”的设计,实现了线程隔离的变量存储,是多线程编程中“空间换时间”思想的典型应用。

其核心源码围绕 ThreadthreadLocals 变量和 ThreadLocalMap 展开:

  • 线程隔离性:变量存在线程自身的 threadLocals 中。
  • 存储机制:ThreadLocalMapThreadLocal 为 key(弱引用)存储副本。
  • 易用性:通过 set()/get()/remove() 简化操作。

使用时需牢记:用完必须调用 remove(),尤其在线程池环境中,否则可能导致内存泄漏或数据污染。合理使用 ThreadLocal,能在数据库连接、上下文传递等场景中大幅简化代码,提升并发效率。


Studying will never be ending.

▲如有纰漏,烦请指正~~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐