竞态条件是多个线程未同步访问共享资源且至少一个执行写操作时,因执行时序不确定导致结果错误的必然性缺陷;根源在于非原子性操作、内存可见性缺失和指令重排序。

竞态条件是指多个线程同时访问同一共享资源,且至少有一个在写入时,未加同步控制,导致最终结果依赖于线程执行的偶然顺序。它不是偶发错误,而是并发逻辑缺陷的必然表现——只要条件满足,就一定会出问题,只是时间早晚而已。
竞态条件的核心成因
它不是“线程跑得太快”,而是操作本身存在结构性漏洞:
-
非原子性操作:比如
counter++看似一条语句,实际分三步——读取旧值、加1、写回新值。任意一步被其他线程打断,就会丢失一次更新。 - 缺乏内存可见性保障:线程A修改了变量,但该修改可能还卡在CPU缓存里,线程B读到的仍是过期副本,于是基于错误前提做后续操作。
- 指令重排序干扰:编译器或处理器为优化性能,可能调整代码执行顺序。例如标志位赋值和数据初始化被调换,导致另一个线程看到“已完成”信号,却读到未初始化的数据。
典型破坏场景举例
这些不是理论假设,而是真实高频出错点:
- 银行账户余额异常:两个取款线程同时读到余额100元,各自扣50元后写回,结果变成50元而非0元——漏掉了一次扣减。
-
计数器统计失真:10个线程各执行1万次
++count,预期结果是10万,实际常为9.8万左右(鸿蒙实测误差率可达38.7%)。 - 文件系统状态混乱:线程A准备创建目录并写文件,线程B在A完成前就尝试向该目录写入,触发“目录不存在”错误,或更糟——两者都创建同名目录,引发覆盖或权限冲突。
为什么synchronized或锁不一定能彻底解决
加锁只是手段,用错照样失效:
- 只保护了写操作,但读操作没加锁——其他线程可能在锁外读到中间态数据。
- 锁粒度太粗,影响性能;太细,容易遗漏临界区,比如忘记对
getCount()方法加锁。 - 锁不解决可见性延迟:synchronized 在释放锁时才刷主存,若另一线程在获取锁前已读取变量,仍可能看到旧值。
- 未配合 volatile 使用:如用布尔标志控制线程启停,标志位不加 volatile,等待线程可能永远循环,因为看不到变化。
真正有效的应对思路
不能只靠“加把锁”,要分层防御:
- 优先消除共享:用 ThreadLocal 存储线程私有数据,或改用不可变对象(Immutable),从源头避开竞争。
-
用原子类型替代普通变量:如 Java 的
AtomicInteger、C++ 的std::atomic<int>,底层通过 CPU 原子指令实现,无锁且高效。 - 锁 + volatile 协同:锁保原子性,volatile 保状态标志的及时可见,两者互补。
-
避免“读-改-写”模式:改用 CAS(Compare-And-Swap)类操作,如
AtomicInteger.compareAndSet(),失败则重试,逻辑更健壮。

















