ThreadLocal那么难吗 进来battle下

ThreadLocal是什么

首先让我们打开ThreadLocal的源码,映入眼帘的第一句类上的介绍注释,在这里插入图片描述这个类提供了线程本地变量,这句话理解起来很容易,这个类作用是:对于同一个变量,不同的线程拥有者这个变量的本地变量也就是副本变量,也就是说每个线程之间对于这个变量的操作相互之间互不影响,如介绍**独立的初始变量拷贝的副本 **在这里插入图片描述

ThreadLocal有什么用

从它的介绍中可以看出,对于共享变量实现了线程隔离的作用,从某种意义上说实现了‘’线程安全‘’。当然从我理解的角度来看,它设计的初衷并不是为了解决线程安全,首先解决线程安全的方式很多,加锁、CAS等,其次线程安全的定义是:对于同一变量或者对象,多个线程同时操作时,该变量或者对象总是能表现出正确的行为,或者说预期的结果,而对于ThreadLocal来说,多个线程操作的并不是同一个变量,而是当前线程的本地变量,用于在当前线程中传递使用这个本地变量。

ThreadLocal的使用场景(案例)

场景一

比如说之前项目有一个需求,有个创建订单的接口,需要在日志中的每条日志记录加上前缀,这个前缀就是订单号,对于每个请求创建订单线程,使用ThreadLocal创建一个本地变量,结合MDC的使用,可以很方便的实现这个需求。

场景二

在做保险核心项目时,业务流程很长,业务逻辑很复杂,每个步骤流程都是一个方法,方法的参数都需要传递一个很大的Map,Map中包含着一个保险订单很多的信息,每个方法之间的执行,都需要携带着这个大Map当做参数,如下demo,当然真正代码处理不是这么简单在这里插入图片描述使用ThreadLocal之后,每个方法就可以避免这种大Map的传递,在这里插入图片描述

案例三

ThreadLocal在很多优秀框架中有着大量的应用,用于处理传递线程的上下文信息,如Spring、Mybatis、Netty等等,其中有个经典应用就是Mybatis中处理数据库的连接,推荐看下这篇博客
https://www.cnblogs.com/jianshuai520/p/8657777.html

ThreadLocal的实现原理

首先让我们来设想一下,如果是我们来设计ThreadLocal,第一步也是最重要的一点是,ThreadLocal肯定得和每个线程有着关联关系,想到线程,第一反应是不是Thread类,那么怎么建立关联关系呢,成为Thread的变量不就可以了吗
在这里插入图片描述
这里重点是Thread类持有的是ThreadLocal的一个静态内部类:ThreadLocalMap,也就是真正存储线程本地变量的地方。
那么为什么ThreadLocalMap要被设计成ThreadLocal的内部类,而并不是Thread的内部类,我理解有以下两个方面的考虑:
1.Thread线程类本身已经携带包含了很多线程需要的变量、上下文信息等,而ThreadLocalMap本身并不是一个Thead所必须的,那么对于TheadLocalMap的操作就无需Thread进行操作,ThreadLocal就是这么一个API,在Thread需要的时候,操作Thread的线程本地变量。
2.Thread类是JDK1.0就存在的类,而ThreadLocal是1.2版本道格李老爷子写的,本着开闭原则,ThreadLocal是对Thread的增强。

对于ThreadLocal内部具体代码的实现,还是推荐去看下源码,代码不多,主要看的还是设计思想和具体使用,提一张ThreadLocal的一个大体结构图
在这里插入图片描述

ThreadLocal的几个问题

  • 为什么ThreadLocalMap的entry要设计成弱引用
  • 为什么value不设计成弱引用
  • 为什么解决Hash冲突要使用开放定址法
为什么使用弱引用

首先我们来谈谈为什么要把ThreadLocal的entry要设计成弱引用

贴个强、软、弱、虚的连接
对引用不是很理解的xdm 可以点击上方连接先学习一波
首先需要理解的是还是我前面说的本地变量这个概念,当我们使用ThreadLocal操作本地变量后,会发生什么?

在这里插入图片描述

由上图可以看到,我们常说的ThreadLocalMap的弱引用就体现在Entry对象的key上面,而这个key是一个ThreadLocal对象

在这里插入图片描述

如上方截图所示,当我们使用ThreadLocal操作本地变量时,set()方法中,给ThreadLocalMap的key赋值的对象是当前的ThreadLocal,而不是有些博客说的什么当前线程对象
关键点是:ThreadLocalMap是Thead类的一个变量,这就意味着
当前ThreadLocal对象在不考虑弱引用的情况下,将会一直被当前线程持有,直到线程对象的生命周期完结
呐,很明显这有一个问题,如果一个线程的生命周期很长,那么它就会带着这个对象一直存活着(强引用),导致这个对象无法被回收,造成内存泄漏,直到线程生命周期结束才会被回收。
当时用弱引用后,当前ThreadLocal对象使用完,不被其他类强引用,由于当前线程对象间接持有的ThreadLocal对象是弱引用,那么当GC(垃圾回收)发生时,就会顺利回收ThreadLocal对象。

为什么value不设计成弱引用

呐,问题又来了,key是弱引用给回收了,那我value呢,为啥不一视同仁呢

原因是不确定value是不是包含着其他强引用,很可能在使用这个value的时候发现,咦,小老弟 ,你咋是个null

but,这么设计之后,是不是就高枕无忧了
不好意思,不是,就像平时很多代码边界条件没考虑到,一到正式环境,整个人就emo了,emmm 怎么会这样呢,我本地不是这样的,开发环境有问题吧在这里插入图片描述

一个老生常谈的问题, 内存泄漏很容易在这种情况下发生,当key设计为弱引用,也就是ThreadLocal对象被GC回收后,那么value在干嘛,小老弟总不能直接把自己给整没了吧,需要明确的一点是,我们使用ThreadLocal时就是为了操作value,由于不能使用弱引用,导致value可能一直在被线程对象强引用,所以我们在操作完value一定要使用TheadLocal提供的remove()方法,将其置位null,以便垃圾回收器正常回收。

不主动remove,一定会发生内存泄漏吗

一般在实际开发中,其实不调用remove方法也不太容易造成内存溢出,因为从存储结构来看,除非创建海量线程,或者说代码中有大量Threadlocal对象且对象体积很大,并且这些线程(比如说线程池)都不释放,导致大量线程内部持有的ThreadLocalMap中对象一直不会释放,

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值