Java内存模型之顺序一致性

顺序一致性内存模型是一个理论上的参考模型,在设计的时候,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参考。

数据竞争与顺序一致性

当程序为正确同步时,就可能会存在数据竞争。Java内存模型规范对数据竞争的定义如下:

在一个线程中写一个变量 ,在另一个线程中读同一个变量 , 而且写和读没有通过同步来排序。

当代码中存在数据竞争时,程序的执行往往产生违反直觉的结果,这个我们之前的博客中已将提到了,如果一个多线程程序没有正确的同步,这个程序将是一个没有数据竞争的程序。JMM对正确的同步多线程程序的内存一致性做出了一下保证:

如果程序是正确同步的,程序的执行将具有顺序一致性,即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。

顺序一致性内存模型

顺序一致性模型是一个被计算机科学家理想化的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性:

(1) 一个线程中的所有操作必须按照程序的顺序来执行

(2) 不管程序是否同步,所有的线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行并且立刻对多有线程可见。

在概念上,顺序一致性模型有一个单一的全局内存,这个内存通过一个左右摆动的开关可以连接到任意的一个线程,同时每一个线程必须按照程序的顺序来执行内存读/写操作串行化(在顺序一致性模型中,所有的操作之间)

为了更好的理解,下面通过两个示例图对顺序一致性模型做进一步的说明:

假设这两个线程使用监视器锁来正确同步:A线程的三个操作执行后释放监视器锁,随后B线程获取同一个监视器锁,那么程序在顺序一致性模型中的执行结果如图所示:

现在我们假设这两个线程没有做同步,下面是未同步程序在顺序一致性模型中的执行示意图:

未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有的线程都只能看到一个一致的整体执行顺序。以上图所示,线程A和线程B看到的结果都是一致的。之所以能得到这个保证是因为顺序一致性内存模型中的每一个操作必须立即对任意线程可见。

但是JMM中就没有这个保证,为同步程序在JMM中不但整体上执行顺序是无序的,而且所有的线程看到的操作执行顺序也可能会不一致。比如,在当前线程把写完的数据缓存在本地内存中,在没有刷新到主内存之前,这个写操作仅仅对当前线程可见;从其他线程的角度来观察,会任务这个写操作根本没有被当前线程执行。只有当前线程把本地内存中写过的数据刷新到主内存之后,这个写操作才能对其他线程是可见的。在这种情况下,当前线程和其他线程看到的操作执行顺序将不一致。

同步程序的顺序一致性效果

class SynchronizedExample{
    int a = 0;
    boolean flag = false;
    public synchronized void writer(){
       a = 1;
        flag = true; 
   }
    public sychronized void reader(){
        if(flag){
            int i = a;
            ......
        }
    }
}

假设线程A执行writer方法后,B线程执行reader方法,这是一个正确同步的多线程程序。根据JMM规范,该程序的执行结果将与该程序在顺序一致性模型中的执行结果相同。顺序一致性模型中,所有的操作完全按照程序串行执行。而在JMM中,临界区的代码可以重排序,但JMM不允许临界区的代码“逸出”到临界区之外,那样会破坏监视器的语义。JMM会在退出临界区和进入临界区这两个关键时间点做一些特别的处理,使得线程在这两个时间点具有顺序一致性模型相同的内存视图。虽然线程A在临界区内做了重排序,但是由于监视器互斥执行的特性,这里的线程B根本没有“观察”到线程A在临界区的重排序。这种重排序既提高了执行效率,有没有改变程序的执行结果。

JMM在具体实现的基本方针是:在不改变(正确同步)程序执行结果的前提下,尽可能的为编译器和处理器的优化打开方便之门。

猜你喜欢

转载自blog.csdn.net/IBLiplus/article/details/83824454