使用ThreadLocal却出现了有并发问题?

本文探讨了Spring MVC中线程安全问题的具体原因,并详细解释了如何通过正确使用ThreadLocal来避免请求间的数据污染。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近同事发现所做的app在未登陆的情况刷feed的时候,相同参数请求,返回的结果确不稳定。于是找到我帮忙排查问题。

听到问题第一反应应该是并发场景下数据安全的问题,于是clone了他们的项目,对着代码开始排查。

使用的是springmvc,每个Controller(单例)都继承于一个BaseController,BaseController实现了IWebContext接口,注入了request、response对象


类图:



首先发现问题:Controller默认为单例, 故request作为成员变量肯定会在并发情况下出现参数相互覆盖的问题,解决方式:

@Override
public void setWebContext(HttpServletRequest request, HttpServletResponse response) {错误错误是使用
    //错误使用
    this.request = request;
    //直接使用参数request
    request.getPrameter("key");
    
    //dosomething
}

//该类其他方法使用request方式,request其实也是放在ThreadLocal中
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();



empty
去掉request、response成员变量,使用线程私有变量,这个问题到此应该解决了?  fix之后再测试发现这个问题依然存在。

仔细回顾下代码,现在成员变量只有一个ThreadLocal 里边放的是userInfo信息。
jdk Thread 实现每个线程都维护了变量为threadLocals的ThreadLocal.ThreadLocalMap。ThreadLocal set、get方法都是在当前线程的ThreadMap中进行读写操作,
ThreadMap是以:ThreadLocal对象为key(set操作传入的key都是同一个对象,ThreadLocal解决key重复的算法,基于一个0x61cc88647递增获取一个新的hash值&整个table的长度-1),在这个实现下保证了每个线程操作的就是当前线程的副本。这里是不会有线程安全问题的。


那为什么还会出现数据相互覆盖问题呢?

BaseController现在表面上看是一个线程安全的类了,那问题出在哪呢? 多线程访问的确是做到了线程安全, 通过测试根据日志分析,在单线程访问下,也可能出现返回结果不是预期的值。 这时候想到了线程池线程复用的情况,在线程复用的情况下,先后访问可能,拿到的是同一个线程对象,这时候ThreadLocal中的数据如果没有被清理掉,同一个线程在下次访问依然可以拿到,然后重新扫描了下代码,确实没有在线程处理完之后清除ThreadLocal的代码逻辑,进一步验证了对问题的定位,然后在interceptor中加入Controller处理完后清除ThreadLocal里边的缓存信息,再测试观察,问题解决了。


总结:使用一项技术的时候,很多时候,不知道原理,拿来主义直接使用,很可能隐藏着一些风险,可能在某个时段集中爆发出来,这对于线上的项目影响是不容小觑的,我们应该在对技术的使用之后,多去看看使用的技术背后的原理,没问题学习了知识,发现问题能让问题尽早暴露

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值