深入了解ThreadLocal

目录

一、基本概念

(一)ThreadLocal 的定义与作用

(二)ThreadLocal 与普通变量的区别

(三)ThreadLocal 的核心应用场景

二、实现原理

(一)内部结构

(二) 源码分析

1、get方法

2、set方法

3、remove

4、withInitial 方法

三、问题分析

(一)内存泄露,与解决办法

1、探测式清理

2、启发式清理

一、基本概念

(一)ThreadLocal 的定义与作用

        ThreadLocal,顾名思义,是线程本地的变量。它的作用是为每个线程创建一个独立的变量副本,使得线程之间对该变量的操作互不干扰,从而避免了多线程环境下的线程安全问题。每个线程都可以独立地修改自己所拥有的变量副本,而不会影响到其他线程的副本。

(二)ThreadLocal 与普通变量的区别

        普通变量在多线程环境中是被多个线程共享的,当多个线程同时对其进行读写操作时,很容易出现线程安全问题,需要通过加锁等方式来保证数据的一致性。而 ThreadLocal 为每个线程提供了一个独立的变量副本,线程之间对变量的操作仅限于自己的副本,不会影响其他线程,无需考虑线程同步问题。

(三)ThreadLocal 的核心应用场景

        数据库连接管理:在数据库操作中,每个线程需要拥有自己的数据库连接,避免多个线程共享一个连接导致的操作混乱。使用 ThreadLocal 可以为每个线程存储一个独立的数据库连接,确保线程对数据库的操作互不干扰。

        用户会话存储:在 Web 应用中,每个用户请求对应一个线程,ThreadLocal 可以存储当前用户的会话信息,使得在处理请求的各个环节中都能方便地获取到用户的会话数据,而无需在方法间频繁传递参数。

二、实现原理

(一)内部结构

        ThreadLocal 的内部结构主要依赖于 ThreadLocalMap,它是 ThreadLocal 的静态内部类。ThreadLocalMap 类似于 HashMap,用于存储线程的变量副本,其中 key 是 ThreadLocal 对象,value 是对应的变量副本。(实际上key并不是ThreadLocal本身,而是它的一个弱引用

(二) 源码分析

1、get方法

获取当前线程的 ThreadLocal 副本的值

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以当前ThreadLocal对象为key,从map中获取Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 获取Entry中的value
            T result = (T)e.value;
            return result;
        }
    }
    // 如果map为null或者Entry为null,设置初始值并返回
    return setInitialValue();
}
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;
}

protected T initialValue() {
        return null;
}

 源码分析:

首先获取当前线程对象。
调用 getMap 方法获取当前线程的 ThreadLocalMap。
如果 ThreadLocalMap 不为 null,则尝试以当前 ThreadLocal 对象为 key 获取对应的 Entry。
如果 Entry 存在,则返回其 value。
如果 ThreadLocalMap 为 null 或者 Entry 为 null,则调用 setInitialValue 方法设置初始值并返回。

2、set方法

设置当前线程的 ThreadLocal 副本的值。

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以当前ThreadLocal对象为key,设置value
        map.set(this, value);
    } else {
        // 如果map为null,创建一个新的ThreadLocalMap
        createMap(t, value);
    }
}

源码分析:

        首先获取当前线程对象。
        调用 getMap 方法获取当前线程的 ThreadLocalMap。
        如果 ThreadLocalMap 不为 null,则调用其 set 方法,以当前 ThreadLocal 对象为 key,设置 value。
        如果 ThreadLocalMap 为 null,则调用 createMap 方法创建一个新的 ThreadLocalMap,并将当前 ThreadLocal 对象和 value 作为第一个键值对存入。

3、remove

移除当前线程的 ThreadLocal 副本的值。

public void remove() {
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        // 从map中移除当前ThreadLocal对象对应的Entry
        m.remove(this);
    }
}

         首先获取当前线程的 ThreadLocalMap。如果 ThreadLocalMap 不为 null,则调用其 remove 方法,以当前 ThreadLocal 对象为 key,移除对应的 Entry。

4、withInitial 方法

Java 8 引入的工厂方法,用于创建具有初始值的 ThreadLocal 实例,提供了比继承 ThreadLocal 并重写 initialValue () 更简洁的方式。

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

参数:接受一个Supplier函数式接口,用于提供初始值。
返回值:返回一个SuppliedThreadLocal对象,它是 ThreadLocal 的子类。

SuppliedThreadLocal 类

SuppliedThreadLocal是 ThreadLocal 的静态内部类,重写了initialValue()方法:

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}

 使用示例

// 创建一个带有初始值的ThreadLocal
ThreadLocal<List<String>> threadLocal = ThreadLocal.withInitial(ArrayList::new);

// 在线程中使用
threadLocal.get().add("element"); // 首次调用get()会触发initialValue()

三、问题分析

(一)内存泄露,与解决办法

引用示意图

threadlocal变量为null时 

        ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。
        此时,ThreadLocalMap中就会出现key为null的Entry,如果我们的强引用不存在的话,那么key就会被回收,也就是会出现我们value没被回收,key被回收,导致value永远存在,出现内存泄漏。

        因此,ThreadLocalMap需要主动清理这些 “无效 Entry”。

 

1、探测式清理

        基于明确的 “key 为 null” 条件,在特定操作中精准定位并清理过期 Entry的机制。它依赖对 Entry 的直接检测(发现 key 为 null),并针对性处理,是 “发现一个清理一个” 的主动行为。

        当ThreadLocalMap执行get()、set()、remove()等操作时,会触达具体的 Entry 位置(通过哈希计算定位桶位)。若检测到当前位置或相邻位置的 Entry 存在key=null(即过期),则直接清理该 Entry,并可能连带清理后续连续的过期 Entry(避免 “哈希链” 中残留过期 Entry)。

2、启发式清理

        基于经验规则或概率判断,在特定场景下触发的 “预防性” 或 “大范围” 清理机制。它不依赖对单个过期 Entry 的精准探测,而是基于 “整体过期 Entry 比例” 或 “操作场景需求”(如扩容前)主动触发更全面的清理,降低内存泄漏风险。

  ThreadLocalMap在某些场景下(如扩容前、或检测到较多过期 Entry 时),会通过遍历全表或大范围扫描的方式,批量清理过期 Entry,减少无效数据占用的空间,避免频繁扩容或内存堆积。其核心是基于 “经验判断”—— 若当前操作可能导致内存占用过高,则提前清理一批过期 Entry。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值