目录
1. volatile的两大特性
可见性(Visibility)
有序性(Ordering)
2. 四大屏障
3. 读写屏障插入策略
happens-before与volatile变量规则:
注意事项
4. 原子性
5. 禁重排
6.使用场景
传统的单例模式实现如下:(多线程环境下会出问题)
DCL单例模式实现
1. volatile的两大特性
可见性(Visibility)
- 当一个线程修改了一个
volatile
变量时,新值会立即被写入主内存,并且其他线程读取该变量时会从主内存中重新读取最新值。这确保了volatile
变量的修改对其他线程立即可见。
有序性(Ordering)
volatile
可以禁止对声明为volatile
的变量前后的操作进行指令重排序优化。即编译器和处理器不会对volatile
变量前后的操作进行重排序,保证代码的执行顺序与程序代码顺序相同。
2. 四大屏障
-
LoadLoad屏障:
- 作用:在Load1之后和Load2之前插入LoadLoad屏障,确保Load1数据的读取操作在Load2及后续读取操作之前完成。
- 目的:防止处理器将后面的读操作提前到前面的读操作之前。
-
StoreStore屏障:
- 作用:在Store1之后和Store2之前插入StoreStore屏障,确保Store1的写入操作在Store2及后续写入操作之前完成。
- 目的:防止处理器将后面的写操作提前到前面的写操作之前。
-
LoadStore屏障:
- 作用:在Load操作之后和Store操作之前插入LoadStore屏障,确保Load操作的数据读取在Store操作的数据写入之前完成。
- 目的:防止处理器将写操作提前到读操作之前。
-
StoreLoad屏障:
- 作用:在Store操作之后和Load操作之前插入StoreLoad屏障,确保Store操作的数据写入在Load操作的数据读取之前完成。
- 目的:这是所有屏障中最强大的一个,它不仅保证了屏障前后的读写操作的顺序,还保证了屏障前的写操作对屏障后的读操作可见。
在volatile
变量的读写场景中,具体会用到以下屏障:
-
读操作:
- 在读取
volatile
变量之前,会插入一个LoadLoad屏障和一个LoadStore屏障,用来防止读取操作被重排序。
- 在读取
-
写操作:
- 在写入
volatile
变量之后,会插入一个StoreStore屏障和一个StoreLoad屏障,用来确保写入操作对其他线程立即可见。
- 在写入
这些内存屏障保证了volatile
变量在多线程环境下的可见性和有序性,是JVM底层对Java内存模型(JMM)的一种实现。需要注意的是,这些屏障的实现细节依赖于具体的CPU架构和JVM的版本。
3. 读写屏障插入策略
happens-before与volatile变量规则:
第一个操作 | 第二个操作:普通读写 | 第二个操作:volatile读 | 第二个操作:volatile写 |
普通读写 | 可以重排 | 可以重排 | 不可以重排 |
volatile读 | 不可以重排 | 不可以重排 | 不可以重排 |
volatile写 | 可以重排 | 不可以重排 | 不可以重排 |
注意事项
- 屏障的顺序性:屏障确保了操作的顺序性,防止了重排序的发生,这对于维护多线程之间的内存可见性至关重要。
- 性能影响:由于屏障会阻止指令的重排序,这可能会对程序的性能产生一定的影响。因此,应该只在确实需要保证可见性和有序性的情况下使用
volatile
变量。 - JVM和CPU架构差异:不同的JVM实现和不同的CPU架构可能会对屏障的实现有所不同,但它们都必须遵守Java内存模型(JMM)的规范。
4. 原子性
volatile的变量复合操作不具有原子性
在多线程环境中,如果你需要一个复合操作具有原子性,仅仅使用volatile
是不够的。你需要使用锁或其他原子操作机制来确保操作的原子性。
5. 禁重排
为了防止指令重排序,可以使用volatile
关键字、锁(如synchronized
)这些机制来禁止重排序。
volatile只是确保了可见性,但是无法保证多个线程可能会同时读取同一个volatile
变量的值,然后基于这个值进行计算,并写回新值,导致结果不正确。需要通过加锁的方式解决。
6.使用场景
- 单一赋值例如i=10这种操作可以,但是复合运算例如类似i++这种操作不可以
- 状态标识,判断业务是否结束
- 开销较低的读操作,写读策略
- DCL双端锁的发布
DCL(Double-Checked Locking)是一种优化单例(Singleton)模式的方法,旨在减少创建单例对象时同步代码块的开销。在传统的单例模式实现中,为了保证单例的唯一性,通常会使用一个同步块来确保在多线程环境中只有一个线程能够创建实例。
传统的单例模式实现如下:(多线程环境下会出问题)
public class Singleton {private static Singleton instance;private Singleton() {// 私有构造函数,防止外部创建实例}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
DCL单例模式实现
DCL单例模式试图通过减少同步块的使用来优化性能。它的核心思想是先检查实例是否已经创建,如果没有创建,再使用同步块来创建实例。这样可以避免在实例已经创建的情况下多次执行同步块。
public class Singleton {private static volatile Singleton instance;private Singleton() {// 私有构造函数,防止外部创建实例}public static Singleton getInstance() {if (instance == null) { // 第一次检查synchronized (Singleton.class) { // 加锁if (instance == null) { // 第二次检查instance = new Singleton(); // 创建实例}}}return instance;}
}