全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2023-10-09_ThreadLocal使用不规范,上线两行泪

您的位置:首页 >> 新闻 >> 行业资讯

ThreadLocal使用不规范,上线两行泪 点击关注公众号,回复”福利”即可参与文末抽奖ThreadLocal是Java中的一个重要的类,其提供了一种创建线程局部变量机制。从而使得每个线程都有自己独立的副本,互不影响。此外,ThreadLocal也是面试的一个重点,对于此网上已经有很多经典文章来进行分析,但今天我们主要分析笔者在项目中遇到的一个错误使用ThreadLocal的示例,并针对错误原因进行深入剖析,理论结合实践让你更加透彻的理解ThreadLocal的使用。 前言Java中的ThreadLocal是一种用于在多线程环境中存储线程局部变量的机制,它为每个线程都提供了独立的变量副本,从而避免了线程之间的竞争条件。事实上,ThreadLocal的工作原理是在每个线程中创建一个独立的变量副本,并且每个线程只能访问自己的副本。 「进一步,ThreaLocal可以在当前线程中独立的保存信息,这样就方便同一个线程的其他方法获取到该信息。」 因此,ThreaLocal的一个最广泛的使用场景就是将信息保存,从而方便后续方法直接从线程中获取。 使用ThreadLocal出现的问题明白了ThreaLocal的应应用场景后,我们来看一段如下代码: ?控制层 ?@RestController @Slf4j @RequestMapping("/user") publicclassUserController{ @Autowire privateUserServiceuserService; @GetMapping("get-userdata-byId") publicCommonResultObjectgetUserData(Integeruid){ returnuserService.getUserInfoById(uid); } ?服务层 ?@Service publicclassUserService{ ThreadLocalUserInfolocals=newThreadLocal(); publicCommonResultUserInfogetUserInfoById(Stringuid){ UserInfoinfo=locals.get(); if(info==null){ //调用uid查询用户 UserInfouserInfo=UserMapper.queryUserInfoById(uid); locals.set(userInfo); } //....省略后续会利用UserInfo完成某些操作 returnCommonResult.success(info); } } (注:此处为了方便复现项目代码进行了简化,重点在于理解ThreaLocal的使用) 先来简单介绍一下业务逻辑,前台通过url访问/user/get-userdata-byId后,后端会根据传入的uid信息查询用户信息,以避免进而根据用户信息执行相应的处理逻辑。进一步,在服务层中会缓存当前id对应的用户信息,避免频繁的查询数据库。 直观来看,上述代码似乎没问题。但最近用户反馈会出现这样一个问题,「就是用户A登录系统后,查询到的可能是用户B的信息,这个问题就很诡异了」。遇到问题不要慌,不妨来看看笔者是如何进行思考,来定位,解决问题的。 首先,用户A登录系统后,前端访问/user/get-userdata-byId时携带的uid信息肯定是用户A的uid信息;进一步,传到控制层getUserData处的uid信息肯定是用户A的uid。所以,发生问题一定发生在UserService中的getUserInfoById方法。 进一步,由于用户传入的uid信息没有问题,那么传入getUserInfoById方法也肯定没有问题,所以问题发生地一定在getUserInfoById中获取用户信息的位置。所以不难得出这样的猜测,「即问题大概率在UserInfo info = locals.get()这行代码。」 为了加深理解,我们再来回顾一下问题。「"即用户A登录,最终却查询到用户B相关的信息"。」 其实,这个问题本质其实在于「数据不一致」。众所周知,造成数据不一致的原因有很多,但归根到底其实无非就是:「“存在多线程访问的资源信息,进一步,多线程的存在导致数据状态的改变原因不唯一”」。 而Spring中的Bean都是单例的,也就是说Bean中成员信息是共享的。换句话说, 如果Bean中会操纵类的成员变量,那么每次服务请求时,都会对该变量状态进行改变,也就会导致该变量成员那状态不断发生改变。 「具体到上述例子,UserService中的被方法操纵的成员是什么?当然是locals这个成员变量啦!」 至此,问题其实已经被我们定位到了,导致问题发生的原因在于locals变量。 说到此,你可能你会疑惑ThreadLocal不是可以保证线程安全吗?怎么使用了线程安全的工具包还会导致线程安全问题? 问题复现况且你说是ThreadLocal出问题那就是ThreadLocal出问题吗?你有证据吗?所以,接下来我们将通过几行简单的代码,复现这个问题。 @RestController @RequestMapping("/th") publicclassUserController{ ThreadLocalIntegeruids=newThreadLocal(); @GetMapping("/u") publicCommonResultgetUserInfo(Integeruid){ IntegerfirstId=uids.get(); StringfirstMsg=Thread.currentThread().getName()+"idis"+firstId; if(firstId==null){ uids.set(uid); } IntegersecondId=uids.get(); StringsecondMsg=Thread.currentThread().getName()+"idis"+secondId; ListStringmsgs=Arrays.asList(firstMsg,secondMsg); returnCommonResult.success(msgs); } } 第一次访问:uid=1 第二次访问:uid=2可以看到,对于第二次uid=2的访问,这次就出现了 Bug,显然第二次获取到了用户1的信息。其实,从这里就可以看出,我们最开始的猜测没有任何问题。 拆解问题发生原因既然知道了发生问题的原因在于ThreadLocal的使用,那究竟是什么导致了这个问题呢?事实上,我们在使用ThreadLocal时主要就是使用了其的get/set方法,这就是我们分析的切入口。先来看下ThreadLocal的set方法。 publicvoidset(Tvalue){ Threadt=Thread.currentThread(); ThreadLocalMapmap=getMap(t); if(map!=null) map.set(this,value); else createMap(t,value); } 可以看到,ThreadLocal的set方法逻辑大致如下: 首先,通过Thread.currentThread获取到当前的线程然后,获取到线程当中的属性ThreadLocalMap。接着,对ThreadLocalMap进行判断,如果不为空,就直接更新要保存的变量值;否则,创建一个threadLocalMap,并且完成赋值。进一步,下图展示了Thrad,ThreadLocal,ThredLocalMap三者间的关系。 回到我们例子,那导致出现访问错乱的原因是什么呢?其实很简单,原因就是 Tomcat 内部会维护一个线程池,从而使得线程被重用。从图中可以看到两次请求的线程都是同一个线程: http-nio-8080-exec-1,所以导致数据访问出现错乱。 那有什么解决办法吗?「其实很简单,每次使用完记得执行remove方法即可」。因为如果不调用remove方法,当面临线程池或其他线程重用机制可能会导致不同任务之间共享ThreadLocal数据,这可能导致意外的数据污染或不一致性。就如我们的例子那样。 总结至此,我们以一个实际生产中遇到的一个问题为例由浅入深的分析了ThreadLocal使用不规范所带来的线程不安全问题。可以看到排查问题时,我们用到的不仅仅只有ThreadLocal的知识,更有多线程相关的知识。 可能平时我们也会抱怨学了很多线程知识,但工作中却很少使用。因为日常代码中基本写不到多线程相关的功能。但事实却是,很多时候只是我们没有意识到多线程的使用。例如,在Tomcat 这种 Web 服务器下跑的业务代码,本来就运行在一个多线程环境,否则接口也不可能支持这么高的并发,并不能单纯认为没有显式开启多线程就不会有线程安全问题。此外,虽然jdk提供很多线程安全的工具类,但其也有特定的使用规范,如果不遵循规范依旧会导致线程安全问题, 「并不是使用了线程安全的工具类就一定不会出问题!」 最后,再多提一嘴,学了的知识一定要用起来,可能你为了应付面试也曾看过ThreadLocal相关的面经,也知道使用ThreadLocal要执行remove,否则可能会导致内存泄露,「但编程的很多东西,确实需要自己实际操作,否则知识并不会凭空进入你的脑海。」 选择了程序员这条路,注定只能不断的学习,大家一起共勉啦!另外,祝大家双节快乐! 点击小卡片,参与粉丝专属福利!! 如果文章对你有帮助的话欢迎 阅读原文

上一篇:2020-11-12_招聘|腾讯研究院招聘法律研究中心实习生 下一篇:2023-12-14_加速算力基础设施建设,「数智说」算力新基建论坛即将启幕

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价