思維導圖:
引言:
在前面的文章中,我們刻意的避免了對java內存模型JMM的介紹。實際上,正是java內存模型決定了對java代碼的重排序。重排序實際就是多個線程對變量改動的不可見的原因,因爲在代碼邏輯上A線程成的操作在B線程的操作之前進行,但是進過JVM進行重排序後,可能B線程的操作就會在A線程之前進行。通過了解java內存模型,我們可以知道,滿足什麼樣的條件,JVM就不會進行重排序。
本章我們會通過原理部分對java內存模型進行介紹。
- 原理部分:先介紹JVM的java內存模型,然後介紹Happens-Before關係,JVM由這個關係決定是否重排序。
一.平臺的內存模型
在共享內存的多處理器體系架構中,每個處理器都擁有自己的緩存,並且定期的與主內存進行協調。所以,當保持最小的緩存一致性時,系統會允許不同的處理器在同一時刻從同一儲存位置看到的值時不同的。如果想要確保在任意時間各個處理器在同一位置看到的值相同則需要相當大的開銷,而這在大多數時間是不需要的。
我們的程序會執行一種簡單假設,想象在程序中只存在唯一的操作順序,而不考慮這些操作在何種處理器上執行,並且在每次讀取變量時,都能會得在執行序列中最近一次寫入該變量的值。這個樂觀的模型被稱爲串行一致性。
在實際操作中,如果多個線程訪問同一位置時,很有可能出現數據不同步的問題。因爲在編譯期生成指令順序時是可以與源代碼中的順序不同。此外,編譯期也可能會把變量保存在寄存器中而不是內存中,處理器也可能採用亂序或並行等方式來執行指令,緩存也可能會改變將寫入變量提交到主存的次序,而且,保存在處理器本地緩存的值在其他處理器是不可見的。這些都會導致多個線程訪問同一個數據但是結果卻不同步的問題。所以,我們在編寫併發程序時需要藉助鎖或者非阻塞同步機制來進行控制。
二.Happens-Before關係
java內存模型爲所有的操作定義了一個偏序關係,稱之爲Happens-Before。如果想要保證執行操作B的線程看到操作A的結果,那麼在A和B之間必須滿足Happens-Before關係,無論他們是否是在同一個線程之中。
Happens-Before關係如下所示:
- 程序順序規則:如果程序中操作A在操作B之前,那麼在線程中A操作將在B操作之前執行。
- 監視器鎖規則:在監視器鎖上的解鎖操作必須在同一個監視器鎖的加鎖操作之前執行。
- volatile變量規則:對volatile變量的寫入操作必須在對該變量的讀取操作之前執行。
- 線程啓動規則:在線程上對Thread.start的調用不許在該線程中執行任何操作之前執行。
- 線程結束規則:線程中的任何操作都必須在其他線程檢測到該線程結束之前執行,或者從Thread.join中成功返回,或者在調用Thread.isAlive時返回false
- 中斷規則:當一個線程在另一個線程中調用intterupt時,必須在中斷線程檢測到interrupt調用之前執行。
- 終結器規則:對象的構造器函數必須在啓動該對象的終結器之前執行完成。
- 傳遞性規則:如果操作A在操作B之前執行,並且操作B在操作C之前執行,那麼操作A必須在操作C之前執行。
以上就是Happens-Before規則,當我們需要最大限度的提升某些類的性能時,我們可以利用此規則實現對某個未被鎖保護的變量的訪問操作進行排序。但是這種操作對執行順序非常敏感,所以特別容易出錯。