首页 > 其他分享 >ThreadLocal 的应用及原理

ThreadLocal 的应用及原理

时间:2023-05-25 14:46:09浏览次数:36  
标签:变量 userId ThreadLocal 线程 引用 应用 Entry 原理

1. 是什么

JDK 对 ThreadLocal 类的描述为:

此类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问一个变量的线程(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些字段希望将状态与线程(例如,用户ID或事务ID)相关联。

说白了,ThreadLocal 就是用来存放线程自身相关数据的一个容器,这个容器叫做ThreadLocalMap,它是 Thread 类的一个成员变量,它本身也是一个哈希表,key 是 ThreadLocal 本身,value 是存入的变量。也就是说,变量是存在当前线程的一个ThreadLocalMap中,每个线程在取这个变量的时候,就是取线程自己的本地变量,自然是线程安全的了,所以说 ThreadLocal 提供线程局部变量,或者叫本地变量。

ThreadLocal 的特点有3个关键点:

  1. 线程并发:在多线程并发的场景下使用。
  2. 数据传递:通过 ThreadLocal ,在同一个线程中,不同组件中传递公共变量。
  3. 线程隔离:不同线程之间互不干扰,这种变量在线程的生命周期内起作用。

2. 怎么用

ThreadLocal 的常用方法有:

  1. public ThreadLocal():通过构造器创建对象。一般是静态的。
  2. <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier):初始化一个 ThreadLcoal。
  3. void set(T value):设置当前线程绑定的局部变量。
  4. T get():获取当前线程绑定的局部变量。
  5. void remove():删除当前线程当定的局部变量。

2.1 使用入门

2.1.1 原始版本

现在模拟一个需求,一个线程在业务开始时初始化一个用户 id(类似在一次web请求中上下文中初始化一下用户信息),业务结束时获取这个用户 id(比如用来打印日志,或者作为一个公共变量运用到业务编码中),存在多个这样的线程。

public class ThreadLocalTest {
    private String userId;

    private String getUserId() {
        return userId;
    }

    private void setUserId(String userId) {
        this.userId = userId;
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i < 6; i++) {
            Thread thread = new Thread(() -> {
                // 当前线程初始化userId
                test.setUserId(Thread.currentThread().getName() + "的userId");
                // 执行其他业务代码
                System.out.println("===执行业务代码===");
                // 当前线程获取userId
                System.out.println(Thread.currentThread().getName() + "->" + test.getUserId());
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

一种可能的结果:

===执行业务代码===
线程2->线程1的userId
===执行业务代码===
线程1->线程3的userId
===执行业务代码===
线程3->线程3的userId
===执行业务代码===
线程4->线程4的userId

由于线程调度的不确定性,可能线程1运行到一半,切换到了线程2,于是线程2获取到的 userId 是线程1设置的。也就是说,每个线程之间的变量不是隔离的,造成数据错误。

2.1.2 ThreadLocal 版本

每个线程中的变量都放到自己线程的ThreadLocal中,所以这些变量叫做线程局部变量很形象。

public class ThreadLocalTest {
    private static ThreadLocal<String> context = new ThreadLocal<>();

    private String getUserId() {
        return context.get();
    }

    private void setUserId(String userId) {
        context.set(userId);
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i < 5; i++) {
            Thread thread = new Thread(() -> {
                test.setUserId(Thread.currentThread().getName() + "的userId");
                System.out.println("===执行业务代码===");
                System.out.println(Thread.currentThread().getName() + "->" + test.getUserId());
                context.remove(); // 使用完清理线程局部变量
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

这样每个线程就互不干扰,不会取错变量值。一种可能的结果如下:

===执行业务代码===
线程1->线程1的userId
===执行业务代码===
线程4->线程4的userId
===执行业务代码===
线程2->线程2的userId
===执行业务代码===
线程3->线程3的userId

2.1.3 synchronized 版本

如果只看结果的正确性,用 synchronized 给业务代码块加锁也是可以完成的。如下:

Thread thread = new Thread(() -> {
    synchronized (ThreadLocalTest.class) {
        test.setUserId(Thread.currentThread().getName() + "的userId");
        System.out.println("===执行业务代码===");
        System.out.println(Thread.currentThread().getName() + "->" + test.getUserId());
    }
});

这样完全可以实现需求,但是 synchronized 的问题是什么呢?我们总说谁谁谁是线程安全的类,因为它有 synchronized 修饰。就是因为 synchronized 让多线程变成了单线程,它一次只允许一个线程执行,它能不安全吗?但它带来的代价是性能的下降,它不能并发执行,而 ThreadLocal 可以并发执行。

2.1.4 ThreadLocal 和 synchronized 对比

综上,synchronized 和 ThreadLocal 两个处理问题的角度和场景是不同的。

  • synchronized 的侧重点在于保证操作的原子性,保证并发场景下共享变量的数据一致性。
  • ThreadLocal 强调线程隔离性,不同的线程互不干扰,保证并发场景下数据传递的正确性。在web请求上下文中较为常见。

3. ThreadLocal 原理

3.1 代码结构

ThreadLocal 的原理要从它的set(T value)get()方法的源码入手。在 set 值的时候,首先会获取当前线程一个的成员变量ThreadLocalMapThreadLocalMap的 key 是当前ThreadLocal对象,value 是要存入的值。这个 key 和 value 会存到哪里呢?ThreadLocalMap还有个内部类Entry,这个Entry继承了WeakReference,key 赋值给弱引用,也就是当前的ThreadLocal对象,value 则赋值给Entry的成员变量valueThreadLocalMap也是一个哈希表(所谓哈希表,也叫散列表,它基于数组,通过某种哈希算法计算出一系列关键字对应的散列值,然后以这些散列值作为数组索引将数据存放到对应位置,达到快速查找的目的),它内部维护一个Entry数组,来存储键值对。存数据的时候也是通过哈希函数计算ThreadLocal 对象对应的数组下标,然后放入Entry数组中。

3.2 内存泄漏问题

ThreadLocal 会发生内存泄漏吗?我们结合代码慢慢分析。

在 2.1.1 节中有这样的代码:

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

    private void setUserId(String userId) {
        threadLocal.set(userId);
    }
    // ...
}

首先,我们new了一个 ThreadLocal 对象,这里存在一个强引用:threadLocal引用变量指向 ThreadLocal 对象。其次,当其他线程执行setUserId方法时,ThreadLocal 的set方法最终是把数据存到了ThreadLocalMap中的Entry,看源码我们会发现,存数据最终是调用Entry的构造器Entry(ThreadLocal<?> k, Object v)完成的,而k这个参数是传入的this对象,说明什么?我们使用 ThreadLocal 对象调用set,那this肯定是当前new出来的 ThreadLocal 对象!再次说明,我们new出来的 ThreadLocal 对象有两个引用指向它:

  1. threadLocal变量的强引用。
  2. 在Entry中的弱引用。

此时再看一张图(这张图被广泛引用,感谢原图作者

标签:变量,userId,ThreadLocal,线程,引用,应用,Entry,原理
From: https://www.cnblogs.com/cloudrich/p/17431133.html

相关文章

  • 淘宝API接口的各种应用
    淘宝API接口是一种强大且多功能的工具,可帮助开发者利用淘宝平台的海量数据并以此更高效地完成各种任务。不论是想构建一个电商网站,还是想开发一个淘宝购物助手,淘宝API都能帮助你实现这些目标。下文将介绍淘宝API接口的各种特性,帮助开发者全面了解这一工具,更好地利用它。获取商品数......
  • 淘宝API接口的各种应用
    淘宝API接口是一种强大且多功能的工具,可帮助开发者利用淘宝平台的海量数据并以此更高效地完成各种任务。不论是想构建一个电商网站,还是想开发一个淘宝购物助手,淘宝API都能帮助你实现这些目标。下文将介绍淘宝API接口的各种特性,帮助开发者全面了解这一工具,更好地利用它。获取商品......
  • 【习题 6】保存应用数据 答案
    【习题6】保存应用数据判断题1.首选项是关系型数据库。正确(True)错误(False)2.应用中涉及到Student信息,如包含姓名,性别,年龄,身高等信息可以用首选项来存储。正确(True)错误(False)3.同一应用或进程中每个文件仅存在一个Preferences实例。正确(True)错误(Fal......
  • 深度学习进阶篇-预训练模型[2]:Transformer-XL、Longformer、GPT原理、模型结构、应用
    深度学习进阶篇-预训练模型[2]:Transformer-XL、Longformer、GPT原理、模型结构、应用场景、改进技巧等详细讲解1.Transformer-XL:AttentiveLanguageModelsBeyondsaFixed-LengthContext1.1.Transformer-XL简介在正式讨论Transformer-XL之前,我们先来看看经典的Transform......
  • Gartner 魔力象限:应用程序安全测试 2023 - Magic Quadrant for Application Security
    MagicQuadrantforApplicationSecurityTesting2023Gartner魔力象限:应用程序安全测试2023请访问原文链接:https://sysin.org/blog/gartner-magic-quadrant-ast-2023/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgGartner魔力象限:应用程序安全测试2023Magic......
  • 红外光学雨量传感器的工作原理
    红外光学雨量传感器的工作原理IFR02红外光学雨量传感器是一种非接触式的雨量监测装置,其工作原理基于红外光学测量技术。 传感器在工作时,通过发射红外光束对测量区域进行照射,当红外光线被雨滴所遮挡时,光束会被反射或散射,此时传感器可以通过接收反射或散射的光线来计算测量区域......
  • 红外光学雨量传感器的工作原理
    红外光学雨量传感器的工作原理IFR02红外光学雨量传感器是一种非接触式的雨量监测装置,其工作原理基于红外光学测量技术。传感器在工作时,通过发射红外光束对测量区域进行照射,当红外光线被雨滴所遮挡时,光束会被反射或散射,此时传感器可以通过接收反射或散射的光线来计算测量区域的雨......
  • 公网对讲SDK——对讲应用场景
    anyRTC公网对讲SDK的发布,解决了开发者想做公网对讲应用但没有技术的困境;SDK全平台支持的特性,让开发者可以在任何平台接入对讲能力;同时因为包体积小、对业务零入侵等特性被运用到各行各业中,下面咱们就来聊一聊公网对讲SDK的使用场景。公网对讲调度系统公网对讲调度系统行业已经......
  • 公网对讲SDK——对讲应用场景
    anyRTC公网对讲SDK的发布,解决了开发者想做公网对讲应用但没有技术的困境;SDK全平台支持的特性,让开发者可以在任何平台接入对讲能力;同时因为包体积小、对业务零入侵等特性被运用到各行各业中,下面咱们就来聊一聊公网对讲SDK的使用场景。公网对讲调度系统公网对讲调度系统行业已经......
  • layui table.exportFile 导出数据常见应用场景
    layuitable.exportFile导出数据常见应用场景置顶jhadjahjhb于2020-12-2216:46:36发布5896收藏8分类专栏:layui文章标签:layui版权layui专栏收录该内容1篇文章0订阅订阅专栏layuitable.exportFile导出数据数据准备工作场景1,导出全部数据场景2,导出......