1. 程式人生 > >Java記憶體模型之順序一致性

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在具體實現的基本方針是:在不改變(正確同步)程式執行結果的前提下,儘可能的為編譯器和處理器的優化開啟方便之門。