定位“共享可变状态+缺乏同步”是排查多线程计数错乱的核心,需确认变量是否被多线程并发读写、检查高危定义、追踪调用链、识别隐式共享,并通过增线程数、加停顿等手段复现竞态,再以AtomicInteger、ConcurrentHashMap等方案验证修复效果。

排查多线程环境下因错误使用非线程安全全局变量导致的计数错乱,核心是定位“共享可变状态 + 缺乏同步”这一组合。不需要盲目加锁或重写全部逻辑,而是按线索逐步验证。
确认变量是否真被多线程并发修改
先排除误判:不是所有全局变量都危险,关键看它是否同时满足两个条件——可变(非 final / const / immutable) 且 被多个线程直接读写同一实例。
- 检查定义位置:如
static Map或counter = new HashMap(); public static int total = 0;—— 这类是高危目标 - 追踪调用链:从线程启动点(如
new Thread(...).start()或executor.submit(...))出发,看是否多个线程最终都执行了对同一变量的++、put()、add()等写操作 - 注意隐式共享:比如单例类中一个非线程安全的成员字段,被多个线程通过不同方法访问,也属于此列
复现并验证竞态行为
计数错乱本质是数据竞争(Race Condition),它具有随机性和不可重现性。可通过控制手段放大暴露:
- 增加线程数量和循环次数(例如 100 个线程各执行 10000 次
counter++),显著提高冲突概率 - 在关键操作前后插入短暂停顿(如
Thread.sleep(1)或LockSupport.parkNanos(100)),人为制造交错执行窗口 - 运行多次并记录结果:若每次输出不一致(如 998765、999102、997430),基本可锁定为竞态问题
检查底层操作是否原子
很多错乱源于把非原子操作当成了原子操作。重点审视你正在修改的语句:
-
counter++、list.add(x)、map.put(k, v)都不是原子操作——它们包含读、算、写三步,中间可能被切换 - 即使变量声明为
volatile int counter,也只能保证可见性,不能解决++的复合性问题 - 若代码中用了
synchronized,检查是否锁的是同一个对象;若锁的是this或临时对象,等于没锁
快速验证修复效果
替换方案要能闭环验证,而非仅“看起来合理”:
- 用
AtomicInteger替代int:改counter++为counter.incrementAndGet(),再运行多次,结果应恒为预期值 - 用
ConcurrentHashMap替代HashMap:改map.put(k, v)为同操作,观察是否不再抛ConcurrentModificationException - 若必须用锁,确保临界区最小化——只包裹真正需要保护的读-改-写段,而不是整个方法或大循环










