ThreadLocal:线程中的全局变量

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

最近接了一个新需求,业务场景上需要在原有基础上新增2个字段,接口新增参数意味着很多类和方法的逻辑都需要改变,需要先判断是否属于该业务场景,再做对应的逻辑。原本的打算是在入口处新增变量,在操作数据的时候进行逻辑判断将变量进行存储或查询。

如果全链路都变更入参和结构,很明显代码上很不优雅,后续如果还要增加业务场景,又需要再改一遍。如果有一个方法可以传递全局变量,而且仅限于当前线程就好了。

到此,会想到有两种解决方案:之前用的比较少的ThreadLocal或者使用redis缓存。考虑到新增字段都是些增删改查的操作,没有必要存到redis中,故使用ThreadLocal。

一、ThreadLocal定义

以微服务架构为例,服务提供方在收到调用方的请求后,会把这个请求分配给一个线程进行处理。一般来说,一个请求会一直由同一个线程处理,中间不会切换线程,所以如果有一个线程中共享的变量,可以当全局变量使用。

ThreadLocal实现的就是一个线程中的全局变量,与真正的全局变量的区别在于ThreadLocal的变量是每个线程中的全局变量,也就是说不同线程访问到的值是不一样的。其填充的变量属于当前线程,该变量对于其他线程是隔离的。

由定义可以发现,ThreadLocal有两个特性:每个Thread的变量只能由当前Thread使用;由于其他线程不可访问,则不存在多线程间共享的问题。

二、修饰

ThreadLocal提供了线程本地的实例,它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。

ThreadLocal变量通常被private static修饰,这样的好处是当一个线程结束时,它所使用的ThreadLocal实例副本都可被回收,避免重复创建。坏处就是这样做可能正好导致内存泄漏。

三、底层实现

ThreadLocal最朴素的内部实现是Map<threadlocal, Object>,这是一个HashMap,又称为ThreadLocalMap。但Java源码并不是Map<threadlocal, Object>的实现。这是因为如果多个线程访问同一个map,这个map需要是线程安全的,构造比较麻烦。Java采用了更简单粗暴的做法:每个线程都有自己的ThreadLocal专属map,里面可以存放多个ThreadLocal变量,这样就解决了多线程同时操作一个map带来的多线程并发问题。

因为要把ThreadLocal的变量当做全局变量使用,需要把变量与初始化函数写在通用的类中,如DDD领域模型中写在Common模块。

具体的实现如下:

public class ThreadLocalUtil {
 
   private static ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();


   public static Integer getScene() {
       return THREAD_LOCAL.get();
   }

   public static void initScene(Integer scene) {
       if (THREAD_LOCAL == null) {
           THREAD_LOCAL = new ThreadLocal<>();
       }
       THREAD_LOCAL.set(scene);
   }

   public static void remove() {
       THREAD_LOCAL.remove();
   }
}

四、致命点

上面提到了的ThreadLocal会带来内存泄露的问题,深入分析下:

一个ThreadLocal实例对应当前线程的一个对象实例,如果把ThreadLocal声明为某个类的实例变量不是静态变量,那么每次创建一个该类的实例就会导致一个新的对象实例被创建。而这些被创建的实例是同一个类的实例,于是同一个线程可能会访问到同一个类的不同实例,这即使不会导致错误,也会导致重复创建同样的对象。如果使用static修饰后,只要相应的类没有被垃圾回收掉,那么这个类就会持有相对应的ThreadLocal实例引用。

ThreadLocal自身并不存储值,而是作为一个key来让线程从ThreadLocal中获取value。ThreadLocalMap中的key是弱引用,所以jvm在垃圾回收时如果外部没有强引用来引用它,ThreadLocal必然会被回收。但是,作为ThreadLocalMap中的key,ThreadLocal被回收后,ThreadLocalMap就会存在null,但value却不为null。如果当前线程一直不结束或者线程结束后不被你销毁,这会产生内存泄露(已分配空间的堆内存由于某种原因未释放或无法释放导致系统内存浪费或程序运行变慢甚至系统奔溃)。

因此,key弱引用并不是导致内存泄露的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,并且没有手动删除对应的value

解决的方法也很简单,只需要打破引用路径中的ThreadLocalMap对对象实例的引用即可。也就是在使用完ThreadLocal之后,必须调用ThreadLocal.remove()。

延伸:

为什么要将Map中的key设置为弱引用呢?

实际上,设置key为弱引用能预防大多数内存泄露的情况。如果key使用强引用,引用的ThreadLocal对象被回收,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,也会导致内存泄露。设置为弱引用后,引用的ThreadLocal对象被回收,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被java GC回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。

参考文章:

https://www.cnblogs.com/tiancai/p/13141234.html?ivk_sa=1024320u

https://last2win.com/2020/09/05/java-threadlocal/

https://blog.csdn.net/u010445301/article/details/111322569

作者:京东零售 李泽阳

来源:京东云开发者社区 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-693415.html

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

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

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

相关文章

  • 单线程、同步、异步、预解析、作用域、隐式全局变量、对象创建、new

    cpu 资源分配的最小单位 一个进程可以有多个线程 cpu 调度的最小单位 线程建立在进程的建立基础上的一次程序的运行单位 线程分为:单线程 多线程 单线程:js是单线程 (同一个时间只能完成一个任务) 多线程:百度是多线程 同步任务是指在主线程上排队的任务,只有当前

    2024年01月22日
    浏览(46)
  • 案例15-ArrayList线程不安全,共用全局变量导致数据错乱问题,占用内存情况

    存入redis的值,可能会出现错误的情况。如果出现错误,接口将会报错。 多个方法一起修改一个 公共变量 的值,造成数据混乱,导致存入redis中的key值错误 还有每次登陆都会重现创建一个对象,放到公共变量中,遇到并发,对象会被大量地创建, 上一个对象会失去引用,等

    2024年02月02日
    浏览(41)
  • Jenkins pipeline中的全局变量

    1.再environment使用key=value形式定义变量 2.从参数中获取变量值

    2024年02月07日
    浏览(34)
  • Java中的全局变量和局部变量(看这篇就够了)

    在Java中,全局变量和局部变量是两种不同作用域的变量。 全局变量(成员变量) : 在类中定义的变量称为成员变量,也叫全局变量。它们的作用域是整个类,在类的任何地方都可以被访问。 如果没有显式地初始化,它们会有默认值。 全局变量可以是基本数据类型(如int、

    2024年02月04日
    浏览(43)
  • jmeter如何将提取到的token值设置为全局变量使其可以跨线程组使用

    我们用到jmeter去进行接口测试时,经常会遇到要跨线程组使用变量的场景,下面用json提取器提取token值并设置为全局变量的方法做为示范: 需要用到的是: 线程组---http请求---json提取器----Beanshell取样器 ;其他元件大家根据自己需要去配置即可,json提取器和正则表达式提取器

    2024年02月16日
    浏览(45)
  • python实现对导入包中的全局变量进行修改

    在写程序中遇到这样一个问题,有一个 base.py 文件,文件内容如下: 在另一个主程序 test.py 中,导入这个文件,并使用其中的 print_x 函数,但是我想修改 base.py 中全局变量 x 的值,于是在 test.py 中的代码写成: 运行 test.py 之后,发现输出仍然是 \\\'base\\\' ,这是因为在 Python 中,

    2024年02月02日
    浏览(49)
  • SpringMVC- ThreadLocal变量的注意点

    在Web应用中,尤其是在使用Spring框架或类似的服务器端Java技术时, ThreadLocal 是一种常用的方式来存储每个请求的用户信息或上下文数据。然而,由于Web服务器通常使用线程池来处理请求,因此理解和正确使用 ThreadLocal 变得至关重要。 线程池和ThreadLocal 在线程池中,线程是被

    2024年01月21日
    浏览(29)
  • 线程安全—ThreadLocal

    定义: ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为每个线程都创建了一个副本,存的是自己,那么每个线程可以访问自己内部的副本变量。 ThreadLocal是用空间换取时间,synchronized是用时间换空间。

    2024年02月11日
    浏览(32)
  • ThreadLocal线程安全示例及其原理

    提示:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的 ThreadLocal是用空间换取时间,synchronized关键

    2024年02月07日
    浏览(39)
  • C++多线程场景中的变量提前释放导致栈内存异常

    在子线程中尝试使用当前函数的资源 ,是非常危险的,但是C++支持这么做。因此C++这么做可能会造成栈内存异常。 上述是一个正常的多线程代码。 但是如果将其中多线程传参设置为引用传递,可能就会造成栈内存异常了,如下所示: 编译成功,但是运行失败。 运行结果:

    2024年02月13日
    浏览(56)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包