并发编程JMM系列之重排序和顺序一致性

前言

昨天我们接触到了什么是Java内存模型以及两种Java并发模型,并对JMM有了一些初步的认识和了解,我们在上节有提到JMM的重排序规则,但是讲的不详细,今天我们再重点聊下重排序这个东西,以及顺序一致性内存模型,OK,开始我们今天的并发之旅吧。

重排序要遵循的三个原则

重排序的目的是提高编译器和处理器的并行处理能力即并行度,但是在做重排序时,一般都会遵循下面3种原则:

数据依赖性原则:如果两个操作访问同一个变量,并且这两个操作中有一个操作是写操作,那么这两个操作之间就存在数据依赖性,上节我们知道编译器和处理器都会进行重排序,其实在进行重排序时都会遵循数据依赖性原则,编译器和处理器不会去改变存在数据依赖关系的两个操作的执行顺序;这就是数据依赖性原则;

当然,数据依赖性指的是在单线程的情况下,多线程情况下数据依赖性不被编译器和处理器考虑;

as-if-serial原则:无论怎么做重排序,程序的执行结果不能被改变,这就是as-if-serial原则;

程序顺序规则:由先行发生原则我们知道,如果A-B,B-C,那么A-C一定成立,但是存在下面一种情况:

                            

根据先行发生A-B-C顺序执行,但是此时我们会发现其实B操作并不依赖A操作的结果,而C操作需要A和B操作的结果可见,所以C的顺序不能重排序,而B可以先执行于A,这种情况下,不仅重排序了,而且B-A-C的执行结果跟A-B-C执行结果一致,这种情况下的重排序JMM认为是合法的,JMM允许这种重排序,这种规则的本意还是在不改变执行结果的前提下,尽可能的提高并行度。

 

重排序对多线程的影响

看下面这段demo:

int a;
 int b;
 public void set() {
   a = 1;
   b = 2;
 }

 public void get() {
   if (b == 2) {
     a++;
   }
   System.out.println(a);
 }

假设set方法由线程1执行,get方法由线程2执行,首先正常没有做重排序下的执行结果:

假设线程1中的set方法,进行了重排序:

再假设线程2种的get方法进行了重排序:

或者:线程2先于线程一执行时,结果又不一样,如下所示

综上我们会发现,重排序在破坏了多线程的语义,所以说在多线程下对存在控制依赖的操作重排序,可能会改变程序的执行结果。

顺序一致性内存模型

顺序一致性内存模型,是一个理论参考模型,我们主要从下面几点展开对顺序一致性内存模型的讲解。

 

数据竞争与顺序一致性

当程序未进行正确同步时就会存在数据竞争问题,那么什么是数据竞争呢?JMM对数据竞争做了如下定义:

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

如果一个多线程程序能正确的同步,那么这个程序将不存在数据竞争,JMM对正确同步的多线程程序的内存一致性做了如下保证:

如果程序是正确同步(指广义上的同步,包括常见同步原语volatile、synchronized等)的,那么程序的执行将具有顺序一致性,也就是说,程序的执行结果与程序在顺序一致性内存模型中的执行结果一致。

顺序一致性内存模型

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

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

  • 所有线程都只能看到一个单一的操作执行顺序

顺序一致性模型如下图:

为了更好的理解,对顺序一致性模型我们再看下面这个例子,假设有两个线程A和B,每个线程都执行3个操作,我们分别看下在线程同步和未同步下,顺序一致性和JMM下的执行结果:

 

同步程序如何保证执行结果一致性

在顺序一致性模型中:所有的操作完全按程序的顺序串行执行,所以同步后的程序是能保证程序执行结果一致性的;

在JMM中:临界区的代码可以重排序,JMM在退出临界区和进入临界区这两个关键时间点做了一些特别处理,使得线程在这两个时间点具有与顺序一致性模型相同的内存视图;

我们还以上面那个例子分析下这两种情况下,同步是如何保证结果一致性的,分析见下图:

我们可以发现,JMM中线程1和线程2在临界区都发生了重排序,而且这种重排序并不会影响结果,在某些情况下,这种重排序是可以提高执行效率的。

 

以上就是我们对JMM重排序和顺序一致性模型的讲解,同时也将顺序一致性模型和JMM模型做了一些对比,希望几分钟的阅读能给你带来收益!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章