处理器内存模型
顺序一致性内存模型是一个理论参考模型,而处理器内存模型在设计时会对顺序一致性模型做一些放松,毕竟如果完全按照顺序一致性的话,处理器很多优化会被禁止。
在两个操作之间不存在数据依赖性的前提下,处理器会对不同类型读、写操作组合的执行顺序放松,常见的有:
- 放松程序中写-读操作的执行顺序,叫做total store ordering内存模型,简称TSO。
- 在前面1的基础上,继续放松程序中写-写操作的执行顺序,叫做partial store order内存模型,简称PSO。
- 在前面1和2的基础上,继续放松程序中读-写、读-读操作的顺序,叫做relaxed memory order内存模型,简称RMO、PowerPC内存模型。
由于常见处理器内存模型比JMM要弱,因此JMM在生成字节码的时候,在指令序列适当位置插入内存屏障指令来禁止处理器重排序。同时由于各个处理器内存模型强弱的不同,为了在不同处理器平台展示一致的内存模型,JMM在不同处理器中需要插入的内存屏障的数量、类型各不相同。
JMM是一个语言级的内存模型。
处理器内存模型是硬件级的内存模型。
顺序一致性内存模型是一个理论级参考模型。
JMM的设计
在设计JMM时需要考虑两个关键因素:
- 程序员对内存模型的使用。从程序员角度来看,内存模型易于理解、易于编程,因此程序员希望是越接近顺序一致性内存模型越好。
- 编译器和处理器对内存模型的实现。希望完全不遵守顺序一致性内存模型,因为这样才能尽可能的优化来提高性能。
程序员希望完全遵守顺序一致性内存模型,编译器和CPU不希望遵守顺序一致性内存模型,那JMM是怎么做到平衡的呢???????????????
JMM针对重排序分为2类:
- 会改变程序执行结果的重排序,这种重排序JMM会要求编译器和CPU必须禁止此类型重排序。
- 不会改变程序执行结果的重排序,这种重排序JMM不对编译器和CPU做要求。
JSR-133对旧内存模型的修改
- 增强volatile内存语义。旧模型允许volatile变量与普通变量重排序,因为CPU会对读-读、写-写、读-写、写-读重排序,鬼知道会重排序成啥样,因此JSR-133限制volatile变量与普通变量的重排序,使得volatile写-读跟锁释放-获取具有相同的内存语义,例如:
int a = 0
volatile b = 0;
int c = b;// 操作1
a = 100;// 操作2
b = 100;// 操作3
c......
旧内存模型可能的重排序结果:(有多种,下面只是其中一种)
b=100;
c=b;
a=100;
新内存模型的重排序结果:
c=b
LoadLoad
LoadStore
a=100
StoreStore
b=100
StoreLoad
这种情况下,编译器通过内存屏障指令禁止重排序了,至于CPU针对内存屏障指令的二次优化,这里不提了。
- 增强final的内存语义。旧模型中多次读取同一个final变量的值可能不相同,因此JSR-133为final增加了读写重排序规则,从而让final具有初始化安全性,例如:
class T{
private int a;
private final int b;
private T t;
private T(){
a=100;
b=100;
}
public static T getT(){
t = new T();
return t;
}
public static int getB(){
return b;
}
public static int getA(){
Return a;
}
}
旧模型可能的重排序:(这种不存在数据依赖性的前提下,编译器和CPU重排序后的结果,鬼知道呢)
class T{
private int a;
private final int b;
private T t;
private T(){
}
public static T getT(){
t = new T();
return t;
}
public static int getB(){
return b;
}
public static int getA(){
a=100;
b=100;
return a;
}
}
因为不存在数据依赖性,这种重排序组合是可能存在的。
新内存模型:
首先,final的初始化必须包含在构造方法内;
其次,构造方法才去把对象引用传出去;
最后,先获取对象引用,然后才去读final变量;