Volatile 能保证原子性吗?为什么?
Volatile 不能保证原子性。
volatile
关键字在 Java 中主要解决 可见性 和 有序性 问题:
可见性:对
volatile
变量的写操作会立即刷新到主内存,且读操作会直接从主内存读取最新值,确保其他线程能立即看到修改。有序性:禁止编译器和处理器对
volatile
变量的读写操作进行重排序。
但它 不提供原子性保证。
volatile
适合单一写线程、多读线程的场景(如状态标志位),不适合需要复合操作的场景。
如何保证原子性?
使用
synchronized
或Lock
使用原子类(如
AtomicInteger
)
Synchronized 是如何保证原子性、可见性、有序性的?
原子性
问题:多线程并发时,操作可能被其他线程中断,导致中间状态被覆盖。synchronized
的解决方案:
互斥锁机制:通过
monitorenter
和monitorexit
指令(对应 Monitor 的获取和释放),确保同一时刻只有一个线程能进入同步代码块。临界区保护:被
synchronized
修饰的代码块(或方法)成为临界区,线程必须持有锁才能执行临界区代码。原子操作保障:锁的排他性保证了临界区内的操作不会被线程切换打断,从而实现了原子性。
可见性
问题:线程修改共享变量后,其他线程可能因本地缓存(工作内存)未更新而读到旧值。synchronized
的解决方案:
内存屏障:
在
monitorenter
(获取锁)时,会插入Load Barrier
,强制从主内存重新加载变量。在
monitorexit
(释放锁)时,会插入Store Barrier
,强制将工作内存的修改刷新到主内存。
Happens-Before 规则:
根据 JMM 规范,对一个锁的解锁操作 Happens-Before 后续对这个锁的加锁操作。
前一个线程在释放锁前的所有修改,对后续获取锁的线程可见。
有序性
问题:编译器和处理器可能对指令重排序,导致代码执行顺序与预期不一致。synchronized
的解决方案:
锁的内存语义:
进入
synchronized
块时(获取锁),相当于执行Acquire
操作,禁止后续读写操作重排序到锁获取之前。退出
synchronized
块时(释放锁),相当于执行Release
操作,禁止前面的读写操作重排序到锁释放之后。
单线程语义:在同步块内部,JVM 会遵守 as-if-serial 语义,即单线程执行结果不会被重排序破坏,但允许不影响结果的重排序。