Java并发中原子性、可见性、有序性是保障共享变量操作正确的三层约束,共同构成happens-before规则基础;volatile提供可见性与部分有序性但不保证原子性,synchronized三者兼具,AtomicInteger通过CAS融合三者。

Java 并发中的原子性、可见性、有序性不是并列的三个独立概念,而是围绕「多线程环境下对共享变量操作的行为约束」形成的三层保障——其中原子性解决“执行不中断”,可见性解决“改了别人看不看得见”,有序性解决“代码写的顺序和实际执行顺序是否一致”。它们共同构成 happens-before 规则的基础,而 happens-before 才是真正用来推理正确性的工具。
原子性 ≠ 单条语句不拆分,而是「不可分割的执行单元」
很多人以为 i++ 是原子的,其实它被编译为「读取 i → 加 1 → 写回 i」三步,中间可被线程抢占。即使 int 类型的读写在 JVM 层面通常是原子的(对 32 位变量),但复合操作仍不安全。
-
AtomicInteger.incrementAndGet()是原子的,因为底层用CAS(Compare-And-Swap)指令保证整个操作不可中断 -
synchronized块或ReentrantLock也能提供原子性,但粒度更大、开销更高 -
long和double的非volatile读写,在 32 位 JVM 上可能不是原子的(JVM 规范允许分两次 32 位操作)
可见性失效常见于「线程本地缓存未及时同步」
每个线程有自己的工作内存(比如 CPU 寄存器或 L1/L2 缓存),对共享变量的修改可能只停留在本地,其他线程看不到最新值。这不是 bug,而是 JVM 为性能做的默认优化。
-
volatile强制每次读都从主内存取,每次写都立即刷到主内存,从而保证可见性 -
synchronized在解锁前会把工作内存中变量的最新值刷新到主内存,加锁时会清空本地缓存并从主内存读取——所以它也具备可见性保障 - 仅靠
final字段能保证初始化完成后的可见性(构造器内写入的 final 值,其他线程看到对象引用后一定能看见该值),但不适用于后续修改 - 没有同步机制时,哪怕只是
flag = true这种简单赋值,另一个线程也可能永远看不到true
有序性被重排序打破,但 happens-before 定义了哪些重排被禁止
JVM 和 CPU 都可能对指令重排序(包括编译器重排、运行期重排、内存系统重排),只要不改变单线程语义。但多线程下,这种重排可能导致诡异结果。
立即学习“Java免费学习笔记(深入)”;
-
volatile写之前的所有操作,不能被重排到写之后;volatile读之后的所有操作,不能被重排到读之前——这就是「volatile 的禁止重排语义」 -
synchronized的解锁操作happens-before后续任意线程对该锁的加锁操作,天然构成有序性边界 - 构造器中对
final字段的写入,happens-before构造器结束,因此其他线程看到该对象引用时,必能看到正确的 final 值 - 不要依赖「代码写在前面就一定先执行」——比如
a = 1; flag = true;中,flag = true可能先于a = 1对其他线程可见,除非加volatile或锁
真正容易被忽略的是:这三个特性从来不会单独起作用。一个 volatile 变量既提供可见性,也提供部分有序性(但不提供原子性);synchronized 同时提供原子性、可见性和有序性;而 AtomicInteger 的 compareAndSet() 方法之所以可靠,是因为它内部融合了 volatile 读写 + CAS,同时满足三者要求。写并发逻辑时,别问“我需要哪个特性”,而要问“我这个场景下,哪些操作必须构成一个 happens-before 关系”。









