【多线程】ThreadLocal 详解,举例说明

不理解多线程的同学可先了解多线程理论篇【多线程】线程是什么?多线程为什么?怎么做?通俗易懂讲解多线程
以及多线程进阶篇【多线程】多线程安全,为什么不安全,要怎么做保证其安全,实例

1、ThreadLocal是什么

ThreadLocal 是 Java 中的一个线程级别的变量,用于在多线程环境下保持变量的独立性。每个线程都可以独立地设置和获取 ThreadLocal 的值,而不会影响其他线程。通常情况下,ThreadLocal 被用来在方法或类之间传递变量。

在这里插入图片描述

如上图所示,每一个线程都拥有一个 ThreadLocalMap,下图是Thread.java源码 。ThreadLocalMap 中存放的是EntryEntryThreadLocalvalue 的映射。

在这里插入图片描述

ThreadLocal 的实现原理是通过维护一个 Map,如图1,其中键为 ThreadLocal 对象本身,值是与 ThreadLocal 对象相关联的数据。。每个线程都有一个唯一的 ID,通过这个 ID 将变量与线程关联起来。

当你调用 ThreadLocalset 方法时,ThreadLocal 实例会在当前线程的 ThreadLocalMap 中存储值。键是 ThreadLocal 实例本身,值是你要存储的数据。

当你调用 ThreadLocalget 方法时,ThreadLocal 实例会在当前线程的 ThreadLocalMap 中查找并返回对应的值。

  • Thread
    线程是Java程序中的基本执行单元。每个线程都有自己的执行栈和程序计数器,但它们之间共享堆内存。在多线程环境下,多个线程可能同时访问相同的变量,可能导致线程安全问题。
  • ThreadLocal
    ThreadLocal 提供了一种将变量与线程关联的机制。它使用 ThreadLocal对象来保存每个线程的变量副本。每个 ThreadLocal 对象都是线程私有的,不同线程之间互不影响。ThreadLocal 通常作为类的私有静态字段存在。
  • ThreadLocalMap
    ThreadLocalMapThreadLocal 内部使用的一个哈希表结构,用于存储每个 ThreadLocal 与其对应值的映射关系。每个 Thread 对象都有一个 ThreadLocalMap ,用于存储该线程所有 ThreadLocal 变量的值。
  • Entry
    Entry是 ThreadLocalMap内部的一个静态内部类,用于表示 ThreadLocal 与值的映射关系。每个 ThreadLocalMap 实际上是一个Entry数组,数组中的每个元素都是一个Entry对象,表示一个 ThreadLocal 与其对应值的映射关系。

在这里插入图片描述
如上图所示 ThreadLocalMap 是 ThreadLocal 的静态内部类,当线程第一次执行set时,ThreadLocal会创建一个ThreadLocalMap对象,设置给Thread的threadLocals变量。当前线程初始化 ThreadLocal 的时候,会将ThreadLocal 作为key值,放在 ThreadLocalMap 中。不同的线程访问的是同一个ThreadLocal,但是用相同的 ThreadLocal,去各自的ThreadLocalMap中找对应的值。这样就能保证该值为自己线程独有。

ThreadLocal的工作原理如下:
下图是 ThreadLocal.java 中的 get()set()方法。

  • 当通过 ThreadLocal 的 set 方法设置变量时,实际上是通过当前线程的ThreadLocalMapThreadLocal 与值关联起来。
  • 当通过 ThreadLocalget 方法获取变量时,实际上是通过当前线程的ThreadLocalMap 查找与之关联的值。
  • ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,以防止内存泄漏。当 ThreadLocal 被回收时,对应的映射关系也会被清理。

这种机制使得每个线程都可以拥有自己的变量副本,互不干扰。ThreadLocal通过这种方式解决了多线程环境下共享变量的线程安全问题。
在这里插入图片描述
在这里插入图片描述

2、为什么使用 ThreadLocal

  • 线程隔离ThreadLocal 提供了一种线程隔离的机制,使得每个线程都可以拥有自己的变量,互不干扰。
  • 避免参数传递: 在多线程环境下,为了传递变量,常常需要在方法之间传递参数。使用 ThreadLocal 可以避免参数传递的繁琐工作,提高代码的简洁性。
  • 线程上下文共享: 在某些情况下,需要在多个方法之间共享某个值,但又不希望使用全局变量。ThreadLocal 提供了一种线程级别的共享机制。

3、ThreadLocal使用方法:

public class Example {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 在线程中设置值
        threadLocal.set("Hello, ThreadLocal!");

        // 在不同的方法中获取值
        method1();
        method2();
    }

    public static void method1() {
        // 从当前线程获取值
        String value = threadLocal.get();
        System.out.println("Method 1: " + value);
    }

    public static void method2() {
        // 从当前线程获取值
        String value = threadLocal.get();
        System.out.println("Method 2: " + value);
    }
}

需要注意的是,在使用完 ThreadLocal 后,尤其是在线程池等场景,应该及时调用 remove 方法,以避免内存泄漏。可以使用 ThreadLocal.remove() 或者使用 try-with-resources 语句块。

public class Example {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        try {
            threadLocal.set("Hello, ThreadLocal!");

            // 在不同的方法中获取值
            method1();
            method2();
        } finally {
            // 及时清理 ThreadLocal,避免内存泄漏
            threadLocal.remove();
        }
    }

    // ...
}

在 Java 8 之后, ThreadLocal 还引入了 withInitial 方法,可以在创建 ThreadLocal 实例的同时初始化其值,进一步简化了使用。

3、实例

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalExample {

    private static final ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
    private static final ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            int value = counter.incrementAndGet();
            threadLocal1.set(value);
            threadLocal2.set("Thread " + value);
            printValues("Thread 1");
        });

        Thread thread2 = new Thread(() -> {
            int value = counter.incrementAndGet();
            threadLocal1.set(value);
            threadLocal2.set("Thread " + value);
            printValues("Thread 2");
        });

        thread1.start();
        thread2.start();
    }

    private static void printValues(String threadName) {
        System.out.println(threadName + " - threadLocal1: " + threadLocal1.get());
        System.out.println(threadName + " - threadLocal2: " + threadLocal2.get());
        System.out.println("---");
    }
}

在这个例子中,两个线程分别递增 counter 并将其值设置到两个不同的 ThreadLocal 变量中。通过 printValues 方法打印每个线程的 ThreadLocal 变量值。由于每个线程有自己的 ThreadLocal 副本,它们之间不会互相干扰。
输出:
在这里插入图片描述

解释: 在这个代码中,有两个线程:thread1 和 thread2。它们共享一个 counter 变量,并使用 threadLocal1 和 threadLocal2 来存储线程本地的值。
.
thread1 启动,counter 的值为0,counter.incrementAndGet() 将 counter 的值增加为1,然后将1设置到 threadLocal1 和 threadLocal2 中。输出线程1的本地值。
.
thread2 启动,counter 的值为1,counter.incrementAndGet() 将 counter 的值增加为2,然后将2设置到 threadLocal1 和 threadLocal2 中。输出线程2的本地值。
.
因为 ThreadLocal 提供了线程本地的变量副本,所以每个线程都可以独立维护自己的值,互不干扰。这就是为什么输出结果中会有 Thread 1 - threadLocal1: 1 和 Thread 2 - threadLocal1: 2 的原因。
.
输出的顺序可能会有差异,因为线程调度的执行顺序不确定。但是每个线程内部的输出是一致的。

更多实践参考:【多线程】ThreadLocal 作为类的私有静态字段实践

持续更新中。。。

  • 37
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值