多个线程同时读写同一变量会导致竞态条件,如count++结果小于预期;volatile无法解决此问题,因不保证复合操作原子性;synchronized和AtomicInteger可解决,前者适合多变量协同,后者适合单变量高频操作;使用synchronized(this)存在锁暴露和继承隐患,应优先选用私有锁对象。

多个线程同时读写同一个变量会出什么问题
当多个线程共享一个可变变量(比如 int count = 0),且至少有一个线程在写,又没有同步措施时,结果往往不是预期的。典型表现是:执行一万次 count++,最终 count 却小于 10000。
根本原因在于 count++ 不是原子操作——它实际包含三步:读取 count 值 → 在寄存器中加 1 → 写回内存。两个线程可能同时读到 5,各自加 1 后都写回 6,导致一次更新丢失。
这种现象叫“竞态条件(Race Condition)”,是并发不安全最常见源头。
volatile 能不能解决 count++ 这类问题
volatile 只能保证变量的可见性和禁止指令重排序,但不保证复合操作的原子性。
立即学习“Java免费学习笔记(深入)”;
也就是说,即使把 count 声明为 volatile int count,count++ 依然会出错。JVM 不会对 volatile 字段的读-改-写序列做原子保护。
适合用 volatile 的场景包括:
• 状态标志位(如 volatile boolean running = true)
• 作为“一次性安全发布”的辅助(配合正确初始化)
• 但绝不适用于需要原子增减、比较并交换等操作的计数器或状态机
synchronized 和 AtomicInteger 怎么选
两者都能解决 count++ 的并发问题,但机制和适用场景不同:
-
synchronized是基于监视器锁的阻塞式同步,适合代码块逻辑较重、涉及多个变量协同更新的场景(比如银行转账:从 A 扣款 + 给 B 加款) -
AtomicInteger底层依赖 CPU 的 CAS(Compare-And-Swap)指令,无锁、轻量,适合单变量高频读写(如计数器、序列号生成) - 注意:
AtomicInteger的getAndIncrement()是原子的,但if (ai.get() > 100) ai.incrementAndGet();这种“先检查后执行”仍需额外同步,否则存在时间窗口漏洞
public class Counter {
private final AtomicInteger atomicCount = new AtomicInteger(0);
private int plainCount = 0;
public void safeIncrement() {
atomicCount.incrementAndGet(); // ✅ 原子
}
public void unsafeIncrement() {
plainCount++; // ❌ 非原子,多线程下不可靠
}
}
为什么 synchronized(this) 容易被忽略的陷阱
用 synchronized(this) 锁住当前实例,看似简单,但隐患明显:
• 如果该对象被外部暴露(比如作为 public 字段、返回值、监听器传入),其他代码可能也对它加锁,造成意外的锁竞争或死锁
• 更严重的是,若子类重写了方法并试图同步,而父类锁的是 this,锁对象语义可能被破坏
更稳妥的做法是使用私有锁对象:
private final Object lock = new Object();
public void doSomething() {
synchronized(lock) {
// 业务逻辑
}
}
这样锁的范围完全可控,不会被外部干扰,也避免了继承带来的不确定性。
真正难的从来不是“要不要同步”,而是“锁什么、锁多久、锁粒度是否合理”。很多并发 bug 源于锁对象选错,或把本该细粒度的锁粗暴地套在整个方法上——既没解决问题,又拖慢性能。











