Java併發程式設計(一)Java記憶體模型
目錄
一、原子性、可見性和有序性
提到併發程式設計就得搞清楚原子性、可見性、有序性這三大性質,搞不清這三個性質,後面的概念也就很模糊。
1、原子性
概念:一個操作或多個操作,要麼全部執行且執行過程不被中斷,要麼不執行。
如何保證原子性:synchronized關鍵字或Lock可以保證原子性,他們可以保證同一時刻只有一個執行緒訪問特定程式碼塊。
2、可見性
概念:多個執行緒訪問同一個變數時,一個執行緒修改了變數的值,其他執行緒能夠立刻看得到修改後的值。
如何保證可見性:Java提供volatile關鍵字保證可見性,被volatile修飾的變數被修改後,會立刻更新到主存,其他執行緒需要讀取時,會強制讀主存。另外,synchronized和Lock也可以保證可見性,因為它們保證任意時刻只有一個執行緒可以訪問,並在釋放鎖之前將修改的值更新到記憶體中。
3、有序性
概念:程式的順序按程式碼邏輯的先後順序執行。
如何保證有序性:由於synchronized和Lock保證同一時刻只有一個執行緒執行,因此它們可以保證有序性;volatile能禁止指令重排,所以也可以一定程度上保證有序性,但volatile只能保證在變數前後的順序分別在變數操作前後分別執行這一點。
二、JMM概念
Java記憶體模型(Java Memory Model, 即JMM)的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣底層細節。
多執行緒的程式執行,由於指令重排和主記憶體和工作記憶體資料不一致性等原因,會導致執行緒安全問題。所謂執行緒安全,是指在擁有共享資料的多條執行緒並行執行的程式中,執行緒安全的程式碼會通過同步機制保證各個執行緒都可以正常且正確的執行,不會出現資料汙染等意外情況。JMM為避免執行緒安全問題提供了模型依據,一方面,JMM為併發程式設計遮蔽了不同處理器記憶體模型的差異,為Java程式設計師呈現一致的記憶體模型;另一方面,JMM為編譯器和處理器的編譯和優化提供規範,避免由於編譯器和處理器的優化導致程式結果混亂。
JMM定義了執行緒和共享記憶體間的關係:每個執行緒有各自的本地記憶體,JMM控制執行緒訪問共享記憶體(即主記憶體)的姿勢,本地記憶體中是執行緒讀/寫變數的副本。顯然,如果不同執行緒主體同時發起讀或寫主記憶體內相同變數的請求,誰先操作導致的結果可能是完全不一樣的。如下圖:
三、指令重排
1、指令重排概念
為了提高程式執行效能,編譯器和處理器常常會對指令進行重排序優化,包括編譯器優化、指令級並行優化、作業系統優化,這些優化都遵循as-if-serial語義:不管怎麼重排序,單執行緒程式的執行結果不能被改變。但在多執行緒情況下,as-if-serial語義並不能保證程式的正確性。
指令重排會導致多執行緒情況下,指令執行結果可能會和應有結果不一致,注意是可能,不是全部,因此JMM會針對有風險的場景禁止指令重排。
2、JMM禁止指令重排
- 針對編譯器重排序,JMM的編譯器重排序規則會禁止一些特定型別的編譯器重排序;
- 針對處理器重排序,編譯器在生成指令序列的時候會通過插入記憶體屏障指令來禁止某些特殊的處理器重排序。
這裡提到記憶體屏障的概念,如果在指令間插入一條記憶體屏障則會告訴編譯器和CPU,不管什麼指令都不能和這條記憶體屏障指令重排序,也就是說通過插入記憶體屏障禁止在記憶體屏障前後的指令執行重排序優化。
四、happens-before規則
happens-before是JMM最核心的概念,用來指定兩個操作之間的執行順序,這兩個操作可以在一個執行緒之內也可以在不同的執行緒中,這種對操作順序的關係的界定可以為程式設計師提供記憶體可見性的保證。
happens-before定義如下:
1.如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前 。
2.兩個操作之間存在happens-before關係,並不意味著Java平臺的具體實現必須要按照happens-before指定的順序來執行。
上述第一點是JMM對程式設計師的承諾,對於程式設計師來說,如果操作A happens-before B,那麼JMM保證了A操作的結果對B可見,且A執行順序排在B之前。第二點是JMM對編譯器和處理器重排序的約束規則,只要不改變程式的執行結果,編譯器和處理器怎麼優化都行。
happens-before具體規則如下:
- 程式順序規則:一個執行緒中的每個操作,happens-before於該執行緒中的任意後續操作。
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
- volatile變數規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
- 傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C。
- start()規則:如果執行緒A執行操作ThreadB.start()(啟動執行緒B),那麼A執行緒的ThreadB.start()操作happens-before於執行緒B中的任意操作。
- join()規則:如果執行緒A執行操作ThreadB.join()併成功返回,那麼執行緒B中的任意操作happens-before於執行緒A從ThreadB.join()操作成功返回。
- 程式中斷規則:對執行緒interrupted()方法的呼叫先行於被中斷執行緒的程式碼檢測到中斷時間的發生。
- 物件finalize規則:一個物件的初始化完成(建構函式執行結束)先行於發生它的finalize()方法的開始。
參考文章:
https://blog.csdn.net/javazejian/article/details/72772461
https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
《Java併發程式設計的藝術》