没啥深入实践的理论系同学,在使用并发工具时,总是认为把HashMap
改为ConcurrentHashMap
,就完美解决并发了呀。或者使用写时复制的CopyOnWriteArrayList
,性能更佳呀!技术言论虽然自由,但面对魔鬼面试官时,我们更在乎的是这些真的正确吗?2021Java面试宝典
1 线程重用导致用户信息错乱
生产环境中,有时获取到的用户信息是别人的。查看代码后,发现是使用了ThreadLocal
缓存获取到的用户信息。
ThreadLocal
适用于变量在线程间隔离,而在方法或类间共享的场景。
若用户信息的获取比较昂贵(比如从DB查询),则在ThreadLocal
中缓存比较合适。
问题来了,为什么有时会出现用户信息错乱?
1.1 案例
使用ThreadLocal存放一个Integer值,代表需要在线程中保存的用户信息,初始null。
先从ThreadLocal获取一次值,然后把外部传入的参数设置到ThreadLocal中,模拟从当前上下文获取用户信息,随后再获取一次值,最后输出两次获得的值和线程名称。
固定思维认为,在设置用户信息前第一次获取的值始终是null,但要清楚程序运行在Tomcat,执行程序的线程是Tomcat的工作线程,其基于线程池。
而线程池会重用固定线程,一旦线程重用,那么很可能首次从ThreadLocal获取的值是之前其他用户的请求遗留的值。这时,ThreadLocal中的用户信息就是其他用户的信息。
1.2 bug 重现
在配置文件设置Tomcat参数-工作线程池最大线程数设为1,这样始终是同一线程在处理请求:
server.tomcat.max-threads=1
先让用户1请求接口,第一、第二次获取到用户ID分别是null和1,符合预期
用户2请求接口,bug复现!第一、第二次获取到用户ID分别是1和2,显然第一次获取到了用户1的信息,因为Tomcat线程池重用了线程。两次请求线程都是同一线程:http-nio-45678-exec-1
。