单例bean数据串号主因是可变成员变量被多线程共享,应检查非static非final非threadlocal字段,结合日志验证实例复用,并用threadlocal临时隔离状态快速验证。
排查基础变量作用域配置不当导致单例 bean 数据串号,核心是定位“本该隔离却共享”的状态变量。问题往往不在于 spring 配置本身,而在于开发者误将请求级或用户级数据存入 singleton bean 的成员字段中。
看 Bean 是否含可变成员变量
这是最直接的突破口。单例 Bean 天然被所有线程共享,一旦它持有非 final、非线程局部的实例变量(如 String currentUser、List
- 逐个检查 service、component 类,重点扫描 非 static、非 final、非 ThreadLocal 声明的字段
- 特别警惕从 SecurityContext、RequestContextHolder、Session 中读取后直接赋值给成员变量的操作
- 用 IDE 的 “Find Usages” 功能追踪该字段的所有读写位置,确认是否跨请求/跨用户复用
验证是否真为单例且被多线程共用
别凭经验假设,用日志或调试实锤。
- 在目标 Bean 的方法入口加日志:log.info("Thread: {}, Bean hash: {}", Thread.currentThread().getName(), this.hashCode());
- 发起两个并发请求(如用 curl 或 JMeter),观察日志中是否出现相同 hash 值但不同线程名
- 若确认是同一实例被多线程调用,再结合上一步发现的可变字段,基本可锁定串号路径
检查作用域配置是否被隐式覆盖
看似是 singleton,但可能被其他机制绕过。
- 确认类上没有误加 @Scope("prototype") 或 @Scope("request") —— 这些会改变默认行为
- 检查是否通过 new XxxService() 手动创建实例并注入到单例中(破坏了容器管理)
- 留意是否使用了 AOP 代理,某些 proxy-target-class 配置异常可能导致实际运行对象与预期不符
用 ThreadLocal 临时隔离状态(快速验证方案)
如果确认问题是成员变量跨线程污染,可用 ThreadLocal 快速验证修复效果。
- 将原字段改为:private static final ThreadLocal
currentUserHolder = ThreadLocal.withInitial(() -> null); - 读写时统一用 currentUserHolder.set(...) 和 currentUserHolder.get()
- 测试并发请求,若串号消失,说明问题根源确系共享成员变量,而非其他逻辑缺陷











