JVM 与内存 整理
volatile 为什么不能让 count++ 变成原子操作?
把可见性、有序性、原子性拆开回答。
先回答:变量加了 volatile,多线程自增为什么仍会丢失更新?
volatile 的读写有可见性与顺序保证,但 count++ 是读取、计算、写回的复合操作。两个线程仍可能基于同一个旧值计算并写入相同结果。需要原子自增时,可以使用 AtomicInteger,或用同一把锁保护整个操作。
三个概念分别回答
- 可见性:一个线程的写入,在规定的同步关系下能被另一个线程观察到。
- 有序性:程序中的内存操作必须遵守 Java 内存模型规定的顺序关系。
- 原子性:一个操作不能被其他线程观察为执行到一半的中间状态。复合操作不能仅凭组成部分可见就获得原子性。
happens-before 不是单纯的时钟先后
happens-before 描述内存模型中的顺序与可见性保证。典型规则包括同一线程的程序次序、同一监视器的解锁与后续加锁,以及对同一个 volatile 字段的写与后续读之间的关系。
如果只是发现日志里 A 比 B 早输出,并不能据此认定所有共享数据已经建立正确的同步关系。
什么时候适合用 volatile
它常用于单独状态标记、正确实现的安全发布等场景。若状态由多个字段共同维护一个不变量,或需要“检查后修改”的原子步骤,就应考虑锁、原子类或更明确的并发结构。
volatile 修饰对象引用,也不意味着对象的每个成员都自动变成 volatile。仍需要分析成员访问与发布之间的同步关系。
延伸阅读
以上为个人整理。涉及版本实现和线上使用时,请结合对应文档与实际测试判断。