内存模型是指给定一段代码和这段代码被CPU执行的顺序,回答该执行顺序是否合法。编译器、Cache、CPU可以自由地调整、优化、修改、删除代码,只要保证最后CPU的执行顺序能被内存模型预测到即可,所以说,内存模型描述了程序的具体行为。
对于Java语言来说,内存模型还可以这样理解:在一些规则的约束下,检查代码执行顺序中的写操作能否被读操作观察到。这些规则被统称为内存模型,在这个模型下,可以确定任意程序点P能否读取到变量V的值。
最严格的内存模型是顺序一致性内存模型(Sequential ConsistencyMemory Model),顺序一致性内存是指程序顺序和执行顺序完全一致,假设有变量v、读操作r、写操作w,那么顺序一致性内存模型还规定:
在执行顺序中,w发生在r前;
在w和r之间,没有其他写操作可以发生。
换句话说,顺序一致性内存模型要求CPU严格按照代码的顺序执行。它禁止了所有编译器优化和处理器重排序,这对于大多数应用来说都是不可接受的。
一种更为宽松的内存模型是happens-before内存模型。不同于顺序一致性内存模型完全禁止优化和重排序,happens-before内存模型只有几条合情合理的约束条件:
1)所有同步动作(加锁、解锁、读写volatile变量、线程启动、线程完成)的代码顺序与执行顺序一致,同步动作的代码顺序也叫作同步顺序。
同步动作中对于同一个monitor,解锁发生在加锁前面。
同一个volatile变量写操作发生在读操作前面。
线程启动操作是该线程的第一个操作,不能有先于它的操作发生。
当T2线程发现T1线程已经完成或者连接到T1,T1的最后一个操作要先于T2所有操作。
如果线程T1中断线程T2,那么T1中断点要先于任何确定T2被中断的线程的操作。
对变量写入默认值的操作要先于线程的第一个操作;对象初始化完成操作要先于finalize()方法的第一个操作。
2)如果a先于b发生,b先于c发生,那么可以确定a先于c发生。
3)volatile的写操作先于volatile的读操作。
只要保证上述条件成立,编译器和CPU就可以自由地对代码进行优化和重排序。代码清单6-8展示了如何使用happens-before内存模型预测执行顺序:
代码清单6-8 使用happens-before内存模型预测执行顺序
public class HappensBefore {
static int x = 0;
static int y = 42;
public static void main(String[] args) {
x = 1;
Thread t = new Thread() {
public void run() {
y = x;
System.out.println(y);
};
};
t.start();
x = y + 1;
}
}
根据happens-before的定义,x=1先于t.start()发生,t.start()先于x=y+1发生,t.start()也先于y=x发生,那么可以预测x=1一定先于x=y+1和y=x发生。但是x=y+1和y=x的先后顺序是不确定的。
happens-before内存模型是Java内存模型的必要不充分条件,它提到的条件是必要的,但是还不能满足Java内存模型的要求。happens-before最致命的问题是它允许值“无中生有”,如图6-2所示。
图6-2a正常情况应该是r1==r2==0,但是也可能出现r1==r2==1。因为左边没有同步操作和volatile,所以读操作可能引发写操作,然后写操作使得每个读操作都能看到它们的值。在图6-2b中,x和y的相互依赖,编译器遇到这种情况时可能会推测y为任意值,最后出现r1==r2==42这种情况,但是对于这个凭空出现的42,显然是不对的。上面提到的这些问题与因果关系有关,Java内存模型扩展了happens-before内存模型,并规定了哪些因果关系是可以接受的,哪些是不可接受的。
Java早在2004年就拥有了一个良好定义的内存模型[1],解决了大量因多线程和并发带来的错误和难以发现的Bug,而C++直到2011年才拥有。新的Java内存模型相当于开发者和硬件系统通过HotSpot VM这个代理人签订的“契约”,该“契约”保证了在一些特定的地方,程序中的代码顺序与硬件的执行顺序一致,而这个一致性的保证通常是由前面提到的内存屏障实现的,如图6-3所示。
当volatile字段和普通字段读写混合时会需要一些内存屏障的支持,总结以上操作可以得到如表6-1所示的内容。
举个例子,假设第一个操作是普通写,第二个操作是volatile写,根据表6-1可知,两个操作之间需要一个StoreStore,这意味着普通写不能被重排序到volatile写后面,volatile写也不能被重排序到普通写前面。除了表6-1中提到的操作,Java内存模型对于final修饰的字段也有一些要求:如果存在对final字段的写操作,那么虚拟机会在后面插入StoreStore屏障,即x.finalField=val; StoreStore。
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。