ThreadLocal 核心原理

ThreadLocal是Java中用于线程隔离的机制,确保每个线程都有自己的变量副本,避免线程安全问题。文章介绍了ThreadLocal的基本概念,包括如何通过set、get和remove方法操作变量,并通过一个示例展示了不同线程间ThreadLocal变量的独立性。此外,还探讨了ThreadLocal的核心原理,涉及到ThreadLocalMap的内部实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、基本概念

在并发编程中,多个线程同时访问同一个共享变量,可能出现线程安全的问题。为了保证在多线程环境下访问共享变量的安全性,通常会在访问共享变量的时候进行加锁,以实现线程同步的效果。
image.png
另外,为了更加灵活地确保线程的安全性,JDK 中提供了一个 ThreadLocal 类,它能够支持本地变量。
ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
可以理解成每个线程都有自己专属的存储容器,它用来存储线程私有变量,其实它只是一个外壳,内部真正存取是一个 Map。每个线程可以通过 set() 和 get() 存取变量,多线程间无法访问各自的局部变量,相当于在每个线程间建立了一个隔板。只要线程处于活动状态,它所对应的 ThreadLocal 实例就是可访问的,线程被终止后,它的所有实例将被垃圾收集。总之记住一句话:ThreadLocal 存储的变量属于当前线程。

//创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();

ThreadLocal的应用场景有

  • 数据库连接池:ThreadLocal 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection,这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B 线程正在使用的 Connection。
  • 会话管理中使用:将Session保存在ThreadLocal中,使线程处理多次处理会话时始终是同一个Session。

在使用 ThreadLocal 类访问共享变量时,会在每个线程的本地内存中都存储一份这个共享变量的副本。在多个线程之间同时对这个共享变量进行读写操作时,实际上操作的是本地内存中的变量副本,多个线程之间互不干扰,从而避免了线程安全的问题。
image.png

2、使用案例

public class ThreadLocalTest {

    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();

    public static void main(String[] args){
        Thread threadA = new Thread(()->{
            THREAD_LOCAL.set("ThreadA: " + Thread.currentThread().getName());
            System.out.println(Thread.currentThread().getName() + "本地变量中的值为: " + THREAD_LOCAL.get());
            System.out.println(Thread.currentThread().getName() + "未删除本地变量,本地变量中的值为: " + THREAD_LOCAL.get());
        }, "Thread-A");

        Thread threadB = new Thread(()->{
            THREAD_LOCAL.set("ThreadB: " + Thread.currentThread().getName());
            System.out.println(Thread.currentThread().getName() + "本地变量中的值为: " + THREAD_LOCAL.get());
            THREAD_LOCAL.remove();
            System.out.println(Thread.currentThread().getName() + "删除本地变量后,本地变量中的值为: " + THREAD_LOCAL.get());
        }, "Thread-B");

        threadA.start();
        threadB.start();
    }
}

输出结果:
Thread-A本地变量中的值为: ThreadA: Thread-A
Thread-A未删除本地变量,本地变量中的值为: ThreadA: Thread-A
Thread-B本地变量中的值为: ThreadB: Thread-B
Thread-B删除本地变量后,本地变量中的值为: null

从输出结果我们可以看到,删除 Thread-B 线程中的本地变量后,Thread-B 线程中保存的本地变量的值为 null。同时,删除 Thread-B 线程中的本地变量后,不会影响 Thread-A 线程中保存的本地变量。

Thread-A 线程和 Thread-B 线程存储在 ThreadLocal 中的变量互不干扰,Thread-A 线程中存储的本地变量只能由 Thread-A 线程访问,Thread-B 线程中存储的本地变量只能由 Thread-B 访问。

3、ThreadLocal 核心原理

ThreadLocal 有四个方法,分别为:

  1. initialValue:返回此线程局部变量的初始值;
  2. get:返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本;
  3. set:将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值;
  4. remove:移除此线程局部变量的值。
3.1 核心原理

在 Thread 类的源码中,定义了两个 ThreadLocal.ThreadLocalMap 类型的成员变量,分别为 threadLocals 和 inheritableThreadLocals,源码如下:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

threadLocals 和 inheritableThreadLocals 二者的初始值都为 null,并且只有当线程第一次调用 ThreadLocal 的 set 方法或者 get 方法时才会实例化变量。
上述代码也说明,通过 ThreadLocal 为每个线程保存的本地变量不是存储在 ThreadLocal 实例中,而是存储在调用线程的 threadLocals 变量中,它的类型为 ThreadLocal 中的一个内部类 ThreadLocalMap,ThreadLocal 是一个壳子,真正的存储结构是 ThreadLocal 里有 ThreadLocalMap 这么个内部类,实现的类似 map 的功能。
每个线程都有自己的一个 map,map 是一个数组的数据结构存储数据,每个元素是一个 Entry,entry 的 key 是ThreadLocal 的引用,也就是当 ThreadLocal 本身并不存储值,它只是作为 key 来让线程从 ThreadLocalMap 获取 value。所以,得出的结论就是 ThreadLocalMap 该结构本身就在 Thread 下定义,而 ThreadLocal 只是作为 key,存储 set 到 ThreadLocalMap 的变量当然是线程私有的。
image.png

3.2 set 方法

ThreadLocal 中的 set 方法的实现逻辑,先获取当前线程,取出当前线程的 ThreadLocalMap ,如果不存在就会创建一个 ThreadLocalMap,如果存在就会把当前的 threadlocal 的引用作为键,传入的参数作为值存入 map 中。代码如下所示:

    public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 以当前线程为 key,获取 ThreadLocalMap 对象
        ThreadLocalMap map = getMap(t);
    	// 获取的 ThreadLocalMap 对象不为空
        if (map != null)
            // 设置 value 的值
            map.set(this, value);
        else
            // 如果为空,实例化 Thrad 类中的 threadLocals 变量
            createMap(t, value);
    }

3.3 get 方法

ThreadLocal 中 get 方法的实现逻辑,获取当前线程,取出当前线程的 ThreadLocalMap ,用当前的threadlocal 作为 key 在 ThreadLocalMap 查找,如果存在不为空的 Entry,就返回 Entry 中的 value,否则就会执行初始化并返回默认的值。代码如下所示:

    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的 threadLocals 成员变量
        ThreadLocalMap map = getMap(t);
        // 获取的 thradLocals 成员变量不为空
        if (map != null) {
            // 返回本地变量对应的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 初始化 threadLocals 成员变量的值
        return setInitialValue();
    }

setInitialValue() 源码如下:

    private T setInitialValue() {
        // 调用初始化 value 的方法
        T value = initialValue();
        Thread t = Thread.currentThread();
        // 以当前线程为 key 获取 threadLocals 成员变量
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 如果 threadLocals 不为空,则设置 value 的值
            map.set(this, value);
        else
            // threadLocals 为空,则实例化 threadLocals 成员变量
            createMap(t, value);
        return value;
    }
3.4 remove 方法

remove 方法的实现比较简单,根据调用的 getMap 方法获取当前线程的 threadLocals 成员变量,如果当前线程的 threadLocals 成员变量不为空,则直接从当前线程的 threadLocals 成员变量中移除当前 ThreadLocal 对象对应的 value 值,源码如下:

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuZhan7710

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值