JMM: 顺序一致性

顺序一致性内存模型是一个理论参考模型

数据竞争与顺序一致性

当程序未正确同步时,就会存在数据竞争。

Java内存模型规范对数据竞争的定义:在一个线程中写一个变量,在另一个线程读同一个变量,而且写和读没有通过同步来排序。

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

这里的同步是指广义上的同步,包括常用同步原语(synchronized、volatile和final)的正确使用,还有Lock锁的正确使用。

顺序一致性内存模型

顺序一致性内存模型的两大特性:

  • 一个线程中的所有操作必须按照程序的顺序来执行。
  • (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序(一个一致的整体执行顺序)。每个操作都必须原子执行且立刻对所有线程可见。

在任意时间点最多只有一个线程可以连接到内存。

未同步的程序在顺序一致性模型中整体执行顺序是未知的(执行结果未知),但是所有线程能看到一个一致的整体执行顺序。

JMM不保证每个操作都对任意线程可见,未同步的程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致。

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

一个正确同步的程序,JMM保证程序的执行结果和顺序一致性模型中的执行结果相同。

JMM在具体实现上的基本方针为:在不改变(正确同步)程序执行结果的前提下,尽可能地为编译器和处理器打开优化之门(例如临界区的代码可以重排序)。

最小安全性

对于未同步未正确同步的多线程程序,JMM只提供最小安全性:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值,JMM保证线程读操作读取到的值不是无中生有的。

JMM与顺序一致性内存模型比较

未同步的程序在JMM和顺序一致性模型中都表现出整体上执行顺序是无序的,其执行结果也无法预知。

未同步的程序在这两个模型中执行的差异:

  1. 顺序一致性模型保证单线程内的操作会按程序的顺序执行,而JMM不保证单线程内的操作会按程序的顺序执行(比如正确同步的多线程程序在临界区内的重排序)。
  2. 顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序。这一点前面也已经讲过,这里就不再赘述。
  3. JMM不保证对64位的long型和double型变量的读/写操作具有原子性,而顺序一致性模型保证对所有的内存读/写操作都具有原子性。

第3个差异与处理器总线的工作机制密切相关。在计算机中,数据通过总线在处理器和内存之间传递。每次处理器和内存之间的数据传递都是通过一系列步骤来完成的,这一系列步骤称之为总线事务(bus transaction)。总线事务包括读事务(read transaction)和写事务(write transaction)。读事务从内存传送数据到处理器,写事务从处理器传送数据到内存,每个事务会读/写内存中一个或多个物理上连续的字。这里的关键是,总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其它所有的处理器和I/O设备执行内存的读/写。

总线的工作机制可以把所有处理器对内存的访问以串行化的方式来执行。在任意时刻,最多只能有一个处理器可以访问内存。这个特性保证了单个总线事务中的内存读/写操作具有原子性

在不同平台上的处理器可能把一个64位的long/double类型的变量的写操作拆分为两个32位的写操作来执行,而这两个32位的写操作可能会被分配到不同的总线事务中执行,此时写操作就不具备原子性。