1.数据竞争
数据竞争:在一个线程中写一个变量,在另一个线程中读同一个变量,而且写和读没有通过同步来排序。数据竞争出现的原因就是成为未正确同步。
顺序一致性:顺序一致性是一种理想的内存模型。如果程序是正确同步的,程序的执行将具有顺序一致性——即程序的执行结果与该程序在顺序一致性内存模型中得执行结果相同。
2.顺序一致性内存模型
顺序一致性内存模型的两大特性:
①一个线程中的所有操作必须按照程序的顺序来执行。
②(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
△在JMM中没有特性②的保证。未同步程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致。(如,当前线程把写过的数据缓存在本地内存而没有刷新到主存,这个写操作只对该线程可见;其他线程则会认为这个写操作没有被当前线程执行)
3.同步程序的顺序一致性效果
class SynchronizedExample{
int a = 0;
boolean flag = false;
public synchronized void writer(){ //获取锁
a = 1;
flag = true;
} //释放锁
public synchronized void reader(){ //获取锁
if(flag){
int i = a;
...
}
} //释放锁
}
假设A线程执行writer()方法后,B线程执行reader()方法。
顺序一致性模型中,所有操作完全被程序的顺序串行执行。而在JMM中,临界区内的代码是可以重排序的(但不允许临界区内的代码“逸出”到临界区外)。两种内存模型中的执行时序对比如下:
△可以看出,JMM在具体实现上的基本方针是:在不改变程序执行结果的前提下,尽可能让编译器和处理器实行优化。
4.未同步程序的执行特性
JMM中对未同步程序的处理
对于未同步或未正确同步的多线程程序,JMM只提供最小安全性(尽可能达到程序安全):线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false),保证线程读操作读取到的值不是无中生有的。
为了实现最小安全性,JVM在堆上分配对象时,首先会对内存空间清零,然后才在上面分配对象(JVM内部会同步这两个操作)。因此,在已清零的内存空间分配对象时,域的默认初始化就已经完成了。
JMM不保证未同步程序的执行结果与该程序在顺序一致性内存模型中的执行结果一致。一是如果想要保证执行结果一致,JMM就必须禁止大量的处理器和编译器优化;二是未同步程序在顺序一致性内存模型的执行本身是无序的,其执行结果无法预知。
未同步程序在两个模型中的执行特性差异
①顺序一致性模型保证单线程内的操作会按程序的顺序执行,而JMM则不保证(如上面临界区内的代码可重排序)。
②顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM则不保证。
③JMM不保证对64位的long型和double型变量的写操作具有原子性,而顺序一致性模型保证对所有内存读写操作的原子性。
△从JSR-133内存模型开始(即从JDK5开始),仅仅只允许把一个64为long/double变量的写操作拆分为两个32位的写操作来执行,任意的读操作在JSR-133中都必须具有原子性。