应主动在开发、测试、上线前多环节设防Java并发安全问题,核心是从共享变量入手,结合执行随机性与非原子操作,用可观测手段暴露竞争条件;需检查静态变量、Spring单例中的非final实例变量、跨线程对象引用、缓存计数器等“共享+可改”场景;通过压力测试观察结果稳定性;借助jcstress、JFR、SpotBugs、Arthas等工具探测竞争;警惕重复处理、状态丢失、死循环等待、半初始化对象、日志乱序等典型现象。

发现 Java 多线程并发安全问题,不能靠等线上报错,而要主动在开发、测试、上线前多个环节设防。核心思路是:**从共享变量入手,结合执行随机性与非原子操作特征,用可观测手段暴露竞争条件**。
看代码里有没有“共享+可改”的变量
这是最直接的切入点。检查是否存在以下情况:
- 静态变量(static 字段)被多个线程读写
- 实例变量被多个线程共用(比如 Spring 单例 Bean 中的非 final 成员)
- 跨线程传递的对象引用,其内部状态被多处修改
- 缓存、计数器、开关标志(如 boolean running = true)被多线程读写但未加保护
只要满足“多个线程能访问 + 可能被修改”,就埋下了隐患。这类代码往往看起来简单,比如一个 count++ 或 list.add(),但恰恰最容易出问题。
跑压力测试时观察结果是否稳定
并发问题常在高并发、低概率下复现。建议做这几件事:
立即学习“Java免费学习笔记(深入)”;
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 用 JMeter 或 JUnit + CountDownLatch 启动 10–100 个线程,反复执行同一段业务逻辑(如扣库存、生成订单号)
- 运行 100 轮以上,看关键结果是否恒定(比如 100 次请求是否总产生 100 条记录、总数是否总是预期值)
- 留意日志中是否出现重复 ID、负库存、空指针、数据覆盖等异常现象
如果结果每次都不一样,或者偶发错误,基本可以锁定为并发安全问题。
借助工具把“看不见的竞争”打出来
有些问题肉眼难见,需要用工具放大线索:
- Thread Sanitizer(TSan):虽原生不支持 Java,但类似原理的 jcstress 工具可做微基准并发测试,专门验证原子性、可见性行为
- JVM 参数 + JFR(Java Flight Recorder):开启 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder,录制运行时线程争用、锁竞争、safepoint 停顿,导出后用 JDK Mission Control 分析
- FindBugs / SpotBugs 插件:能扫描出 non-volatile field access、unsynchronized mutable static field 等典型风险模式
- Arthas trace 命令:在线上小流量环境对关键方法(如 updateStatus())做实时追踪,观察是否有多线程同时进入、返回值是否异常
关注那些“本不该发生”的现场表现
有些现象是并发问题的强信号,看到就要警觉:
- 同一个请求处理了两次(比如消息重复消费、支付回调重复扣款)
- 状态更新丢失(如用户禁用后又变启用,中间某次修改没生效)
- while 循环死等不退出(while(!flag),但 flag 已被其他线程设为 true 却没被看到)
- 对象初始化未完成就被使用(如双重检查锁单例返回 null 或半初始化实例)
- 日志时间戳乱序、线程名频繁切换、堆栈中大量 BLOCKED/WAITING 状态
这些不是孤立 bug,而是底层原子性或可见性被破坏的外在体现。

















