併發編程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模型做了一些對比,希望幾分鐘的閱讀能給你帶來收益!!!

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