一、有序性
1. 什么是有序性?
有序性指程序执行的顺序与代码的先后顺序一致。但在实际执行中,为了提高性能,编译器和处理器可能会对指令进行重排序。
2. 为什么需要有序性?
若线程A和线程B对共享变量的操作顺序被重排,可能导致以下问题:
- 可见性问题:线程A的修改未及时对其他线程可见。
- 逻辑错误:程序依赖的操作顺序被破坏,如单例模式的双重检查锁定。
二、指令重排序
int a = 1;
int b = 2;
可能的执行顺序:
编译器或处理器可能将b = 2
提前到a = 1
之前执行,但不会影响单线程结果。
三、volatile
的禁止重排序机制
1. volatile
的内存语义
volatile
通过以下两种方式保证有序性:
- 禁止重排序:限制编译器和处理器对
volatile
变量相关指令的重排序。 - 内存屏障:插入特定屏障指令,确保操作顺序符合预期。
2. volatile
变量的读写规则
-
写操作:
在写volatile
变量后插入StoreStore屏障(禁止之前的普通写与volatile
写重排序)和StoreLoad屏障(确保写操作全局可见)。 -
读操作:
在读volatile
变量前插入LoadLoad屏障(禁止之后的普通读与volatile
读重排序)和LoadStore屏障(禁止之后的普通写与volatile
读重排序)。
四、实战案例:双重检查锁定
1. 问题代码(无volatile
)
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new Singleton(); // 隐患:可能发生指令重排序
}
}
}
return instance;
}
}
2. 隐患分析
对象初始化操作instance = new Singleton()
可能被重排序为:
- 分配内存空间。
- 将引用
instance
指向内存空间(此时instance != null
)。 - 初始化对象(执行构造函数)。
若步骤2和3被重排,其他线程可能拿到未初始化的对象。
3. 解决方案:使用volatile
private static volatile Singleton instance; // 添加volatile
- 禁止重排序:确保对象的初始化操作完成后,才将引用赋值给
instance
。 - 内存可见性:保证其他线程能立即看到
instance
的最新值。
五、volatile
的局限性
场景 | volatile 是否适用 | 原因 |
---|---|---|
复合操作(如i++) | 不适用 | volatile 无法保证原子性 |
多变量依赖的原子操作 | 不适用 | 需使用锁或原子类(如AtomicInteger ) |
简单的状态标志 | 适用 | 适合单个变量的可见性控制 |