排查全局变量引发的竞态条件需识别共享、验证竞争、定位临界点、验证修复:重点检查static可变容器、并行流中非final静态变量、lambda捕获的共享引用及非线程安全静态缓存;通过万级数据+高并行压力复现波动结果;用jstack比对线程堆栈定位争抢点;验证volatile、锁一致性、atomic替换及collect替代foreach等修复是否使输出稳定。

排查因全局变量作用域过大引发的严重竞态条件,核心在于“识别共享、验证竞争、定位临界点、验证修复”。这不是单靠日志或堆栈就能解决的问题,必须结合代码结构、运行时行为和压力特征系统推进。
一、快速识别被意外共享的全局状态
全局变量本身不危险,危险的是它被多个线程无保护地读写。重点检查以下几类高风险模式:
- 声明在类外部或static字段中的可变容器(如static List
results = new ArrayList() ) - 在并行流(parallelStream())或线程池任务中直接修改的非final静态变量
- 被Lambda捕获且在多线程上下文中反复赋值的对象引用(如Consumer
handler = s -> counter++ ,其中counter是static int) - 工具类中缓存了静态Map/Cache但未加锁或未用ConcurrentHashMap
二、构造可复现的竞争场景
竞态问题常在低并发下“看似正常”,必须主动制造冲突窗口:
- 将数据量放大到万级,用ForkJoinPool.commonPool().setParallelism(8)强制启用多线程,并循环执行100+次,观察输出是否波动(如sum忽为9998、忽为10005)
- 在疑似写操作前后插入带线程名的日志:log.info("[{}] entering write section", Thread.currentThread().getName())
- 用jstack
抓取多次堆栈,比对是否多个线程卡在同一个方法内部(例如都停在ArrayList.add(E)的某一行)
三、定位真实临界区与内存可见性漏洞
很多问题表面是竞态,实则是可见性缺失——一个线程改了,另一个线程根本看不到:
- 检查变量是否缺少volatile修饰(适用于简单布尔开关或状态标记)
- 确认所有读写是否发生在同一把锁内;若用了synchronized,注意锁对象是否一致(synchronized(this)在多实例下无效)
- 避免“伪共享”:多个volatile字段或Atomic变量被CPU缓存行打包在一起,频繁写入会引发缓存行失效风暴——可考虑用@Contended(JDK8+)隔离
四、验证修复方案是否真正生效
不要只看“不报错”,要看“结果稳定”:
- 将ArrayList换成CopyOnWriteArrayList后,再跑100轮压力测试,确认集合size始终等于预期值
- 用AtomicInteger替代int计数器后,在日志中打印incrementAndGet()的返回值,确认无重复或跳变
- 改用stream.parallel().map(...).collect(Collectors.toList())后,对比原始forEach+add方式,验证输出一致性与耗时变化
- 若引入ThreadLocal,务必检查是否遗漏remove()导致内存泄漏(尤其在线程池复用场景)











