1. 程式人生 > >知識點整理2:Java記憶體模型

知識點整理2:Java記憶體模型

原子性、記憶體可見性、重排序、順序一致性、volatile、鎖、final

一、原子性

原子性操作指相應的操作是單一不可分割的操作。例如,對int變數count執行count++d操作就不是原子性操作。因為count++實際上可以分解為3個操作:(1)讀取變數count的當前值;(2)拿count的當前值和1做加法運算;(3)將加完後的值賦給count變數。

在多執行緒環境中,非原子操作可能會受其他執行緒的干擾。比如,上述例子如果沒有對相應的程式碼進行同步(Synchronization)處理,則可能出現在執行第2個操作的時候,count變數的值已經被其他執行緒修改過了。當然,synchronized關鍵字可以幫助我們實現原子性操作,以避免這種執行緒間的干擾情況。

synchronized關鍵字可以實現操作的原子性,其實質是:通過該關鍵字所包括的臨界區(Critical Section)的排他性保證在任何一個時刻只有一個執行緒能夠執行臨界區中的程式碼,這使得臨界區中的程式碼代表了一個原子操作。這一點,大家基本都很清楚。但是,synchronized關鍵字所起到的另一個作用——保證記憶體的可見性(Memory Visibility),也是我們值得回顧的地方。

二、記憶體可見性

  CPU在執行程式碼的時候,為了減少變數訪問的時間消耗可能將程式碼中訪問的變數的值快取到該CPU快取區中,因此,相應的程式碼再次訪問該變數的時候,相應的值可能從CPU快取中而不是主記憶體中讀取的。同樣的,程式碼對這些被快取過的變數的值的修改也可能僅是被寫入CPU快取區,而沒有寫入主記憶體。由於每個CPU都有自己的快取區,因此一個CPU快取區中的內容對於其他CPU而言是不可見的。這就導致了在其他CPU上執行的其他執行緒可能無法“看到”該執行緒對某個變數值的更改。這就是所謂的記憶體可見性。

synchronized關鍵字的另一個作用就是保證了一個執行緒執行臨界區中的程式碼時,所修改的變數值對於稍後執行該臨界區的執行緒來說是可見的。這對於保證多執行緒程式碼的正確性來說非常重要。

而volatile關鍵字也能夠保證記憶體可見性。即一個執行緒對一個採用volatile關鍵字修飾的變數的值的更改,對於其他訪問該變數的執行緒而言總是可見的。也就是說,其他執行緒不會讀到一個“過期”的變數值。因此,有人將volatile關鍵字和synchronized關鍵字所代表的內部鎖做比較,將其稱為輕量級的鎖。這種稱呼其實並不恰當,volatile關鍵字只能保證記憶體可見性,它並不能像synchronized關鍵字所代表的內部鎖那樣能夠保證操作的原子性。volatile關鍵字實現記憶體可見性的核心機制是:當一個執行緒修改了一個volatile修飾的變數的值時,該值會被寫入主記憶體(即RAM)而不僅僅是當前執行緒所在的CPU的快取區,而其他CPU的快取區中儲存的該變數的值也會因此而失效(從而得以更新為主記憶體中該變數的“新值”)。這就保證了其他執行緒訪問該volatile修飾的變數時,總是可以獲取到該變數的最新值。

 

三、指令重排序

volatile關鍵字的另一個作用是:它禁止了指令重排序(Re-order)。編譯器和CPU為了提高指令的執行效率可能會進行指令重排序,這使得程式碼的實際執行方式可能不是按照我們所認為的方式進行。例如下面的例項變數初始化語句:

private SomeClass someObject = new SomeClass();

上述語句非常簡單:(1)建立類SomeClass 的例項;(2)將類SomeClass 的例項的引用賦給變數someObject 。但是由於指令的重排序作用,這段程式碼的實際執行順序可能是:(1)分配一段用於儲存SomeClass 例項的記憶體空間;(2)將對該記憶體空間的引用賦給變數someObject;(3)建立類SomeClass 的例項。因此,當其他執行緒訪問someObject變數的值時,其得到的僅是指向一段儲存SomeClass 例項的的記憶體空間的引用而已,而該記憶體空間相應的SomeClass 例項的初始化可能尚未完成,這就可能導致一些意想不到的結果。而禁止指令重排序則是可以使得上述程式碼按照我們所期望的順序(正如程式碼所表達的順序)來執行。

禁止指令重排序雖然導致編譯器和CPU無法對一些指令進行可能的優化,但是它某種程度上讓程式碼執行看起來更符合我們的期望。

Volatile、synchronized兩者的區別聯絡

1.volatile本質是在告訴jvm當前變數在暫存器(工作記憶體)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住。
2.volatile僅能使用在變數級別;synchronized則可以使用在變數、方法、和類級別的。
3.volatile僅能實現變數的修改可見性,不能保證原子性(執行緒A修改了變數還沒結束時,另外的執行緒B可以看到已修改的值,而且可以修改這個變數,而不用等待A釋放鎖,因為Volatile 變數沒上鎖);而synchronized則可以保證變數的修改可見性和原子性。
4.volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞和上下文切換。
5.volatile標記的變數不會被編譯器優化;synchronized標記的變數可以被編譯器優化。

6.在使用volatile關鍵字時要慎重,並不是只要簡單型別變數使用volatile修飾,對這個變數的所有操作都是原子操作。當變數的值由自身決定時,如n=n+1、n++ 等,volatile關鍵字將失效。只有當變數的值和自身無關時對該變數的操作才是原子級別的,如n = m + 1,這個就是原級別的。所以在使用volatile關鍵時一定要謹慎,如果自己沒有把握,可以使用synchronized來代替volatile。

7.“鎖是昂貴的”,謹慎使用鎖機制。