一文读懂ThreadLocal

这篇具有很好参考价值的文章主要介绍了一文读懂ThreadLocal。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

ThreadLocal 有什么用?

如何使用 ThreadLocal?

ThreadLocal 原理了解吗?


ThreadLocal 有什么用?

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?

JDK 中自带的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

再举个简单的例子:两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。

如何使用 ThreadLocal?

相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。下面简单演示一下如何在项目中实际使用 ThreadLocal 。

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

     // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
    private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());

        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}

输出结果:

Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm

从输出中可以看出,虽然 Thread-0 已经改变了 formatter 的值,但 Thread-1 默认格式化值与初始化值相同,其他线程也一样。

上面有一段代码用到了创建 ThreadLocal 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA 会提示你转换为 Java8 的格式(IDEA 真的不错!)。因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法withInitial(),将 Supplier 功能接口作为参数。

private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
    @Override
    protected SimpleDateFormat initialValue(){
        return new SimpleDateFormat("yyyyMMdd HHmm");
    }
};

ThreadLocal 原理了解吗?

从 Thread类源代码入手。

public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
}

从上面Thread类 源代码可以看出Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 setget方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()set()方法。

ThreadLocal类的set()方法。

public void set(T value) {
    //获取当前请求的线程
    Thread t = Thread.currentThread();
    //取出 Thread 类内部的 threadLocals 变量(哈希表结构)
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 将需要存储的值放入到这个哈希表中
        map.set(this, value);
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

通过上面这些内容,我们足以通过猜测得出结论:最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

比如我们在同一个线程中声明了两个ThreadLocal对象的话, Thread内部都是使用仅有的那个ThreadLocalMap存放数据的,ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal对象调用set方法设置的值。

ThreadLocal 内存泄露问题是怎么导致的?

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()get()remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

弱引用:

如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。文章来源地址https://www.toymoban.com/news/detail-641776.html

到了这里,关于一文读懂ThreadLocal的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一文读懂java变量类型

    前言 在学习和使用Java编程语言时,理解变量类型是至关重要的基础知识。Java是一种静态类型语言,强调变量必须先声明其类型,才能进行后续操作。因此,对于初学者来说,了解Java中不同的变量类型及其特性是迈向编程成功的第一步。 本文旨在帮助大家快速、简洁地了解

    2024年02月09日
    浏览(40)
  • 一文读懂3D开发工具HOOPS SDK

    前言 Tech Soft 3D是全球领先的3D领域开发工具提供商,可帮助软件团队交付成功的应用程序。Tech Soft 3D成立于1996年,该公司的工具包产品为全球数亿台计算机上运行的近500个独特应用程序提供支持,已经助力SolidWorks、AutoDesk、GrabCAD、CD-adapco等客户获得商业成功。 HOOPS是什么?

    2024年02月01日
    浏览(49)
  • 【单元测试】一文读懂java单元测试

    单元测试 是软件开发中常用的一种测试方法,用于验证代码的单个功能单元是否按照预期工作。 测试方法: 白盒测试(White Box Testing):在白盒测试中,测试人员了解代码的内部结构和实现细节,编写测试用例来覆盖不同的代码路径和逻辑条件。 黑盒测试(Black Box Testing)

    2024年04月17日
    浏览(45)
  • 一文读懂3D开发工具HOOPS Platform是什么

    一、HOOPS Platform是什么? 搭建桌面、WEB或移动端的3D应用 弹性且跨平台体验一致的集成架构 工业领域的3D专家提供技术服务 成熟技术已运行于全球98%的电脑中 了解更多HOOPS中文网 慧都科技是HOOPS全套产品中国地区指定授权经销商,提供3D软件开发工具HOOPS售卖、试用、中文试

    2024年01月17日
    浏览(42)
  • 【Java 并发编程】一文读懂线程、协程、守护线程

    在 Java 线程的生命周期一文中提到了 就绪状态的线程在获得 CPU 时间片后变为运行中状态 ,否则就会在 可运行状态 或者 阻塞状态 ,那么系统是如何分配线程时间片以及实现线程的调度的呢?下面我们就来讲讲线程的调度策略。 线程调度是指系统为线程分配 CPU 执行时间片

    2023年04月08日
    浏览(60)
  • 一文读懂大型语言模型参数高效微调:Prefix Tuning与LLaMA-Adapter

    芝士AI吃鱼 在快速发展的人工智能领域中,高效、有效地使用大型语言模型变得日益重要,参数高效微调是这一追求的前沿技术,它允许研究人员和实践者在最小化计算和资源占用的同时,重复使用预训练模型。这还使我们能够在更广泛的硬件范围内训练AI模型,包括计算能

    2024年01月17日
    浏览(53)
  • 一文读懂从 CPU 多级缓存 & 缓存一致性协议(MESI)到 Java 内存模型

    参考:Java Memory Model 多级缓存的出现解决了CPU处理速度和内存读取速度不一致的问题,但是同时也带来缓存不一致的问题,为了解决这个问题,我们引入了缓存一致性协议,常见的缓存一致性协议有MSI,MESI,MOSI,Synapse,Firefly及DragonProtocol等等,下文以MESI协议进行讲述。 缓

    2024年02月05日
    浏览(73)
  • 导致JVM内存泄露的ThreadLocal详解

    很常见的关于ThreadLocal的面试题的问法: 1.说说你对ThreadLocal的理解。 2.ThreadLocal 是什么?有哪 些使用场景?什么是线程局部变量? 3.ThreadLocal内存泄漏分析与解决方案。 ps:想理解好ThreadLocal,必须先得理解好JVM的内存模型 多个线程共同操作一个共享变量,一定会引发并发问

    2024年02月09日
    浏览(49)
  • 【JVM篇】ThreadLocal中为什么要使用弱引用

    ThreadLocal可以在线程中存放线程的本地变量,保证数据的线程安全 ThreadLocal是这样子保存对象的: 在每个线程中,存放了一个ThreadLocalMap对象,本质上就是一个数组实现的哈希表,里面存放多个Entry对象 每个Entry对象继承自弱引用,内部存放ThreadLocal对象,同时用强引用,引用

    2024年02月20日
    浏览(54)
  • 大厂面试题一文讲通jvm,Java虚拟机高频面试题

    薪资范围:6-16K 一个类完整的生命周期,会经历五个阶段,分别为: 加载、连接、初始化、使用 、和 卸载 。其中的连接又分为 验证、准备 和 解析 三个步骤。如下图所示 加载(Loading) 简单一句话概括,类的加载阶段就是: 找到需要加载的类并把类的信息加载到jvm的方法

    2024年01月18日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包