理解Java Memory Model (JMM) 中的 happens-before 原則對于編寫并發程序有很大幫助。
Happens-before 關系是 JMM 用來描述兩個操作之間的內存可見性以及執行順序的抽象概念。如果一個操作 A happens-before 另一個操作 B (記作 A hb B),那么 JMM 向你保證:
- A 的結果對 B 可見: 操作 A 的所有內存寫入操作,對于操作 B 來說都是可見的。也就是說,當執行操作 B 時,操作 A 之前對共享變量的修改值能夠被 B 讀取到。
- A 的執行順序先于 B: 在時間順序上,操作 A 在操作 B 之前發生。編譯器和處理器在重排序指令時,不會改變happens-before 關系的操作的順序(如果改變了會影響可見性或結果)。
需要注意的是,happens-before 并不是說操作 A 必須在操作 B 之前執行。它只是一種順序和可見性保證。如果兩個操作之間沒有 happens-before 關系,那么 JVM 可以對它們進行任意重排序,一個線程的修改對另一個線程也是不可見的。
JMM 定義了一系列的天然的 happens-before 原則,這些原則構成了所有并發操作的基礎:
-
程序順序規則 (Program Order Rule):
- 在一個線程內,按照程序代碼的順序,書寫在前面的操作 happens-before 書寫在后面的操作。
- 重要性: 這是最基本的保證,但僅限于單線程內。它不保證指令不會重排序(只要重排序不影響單線程內的結果),也不保證這些操作對其他線程的可見性。
-
管程鎖定規則 (Monitor Lock Rule):
- 對一個管程(monitor,也就是 Java 中的內置鎖或
synchronized
關鍵字)的解鎖操作 happens-before 隨后對這個管程的加鎖操作。 - 重要性: 這是
synchronized
實現可見性的基礎。當一個線程釋放鎖時,會將工作內存中的共享變量寫回主內存;當另一個線程獲取同一個鎖時,會清空工作內存,從主內存讀取共享變量的最新值。
- 對一個管程(monitor,也就是 Java 中的內置鎖或
-
Volatile 變量規則 (Volatile Variable Rule):
- 對一個
volatile
變量的寫入操作 happens-before 隨后對這個volatile
變量的讀取操作。 - 重要性: 確保了
volatile
變量的可見性。一個線程修改volatile
變量后,這個修改會立即對其他線程可見。volatile
變量的讀寫還會形成內存屏障,禁止特定類型的指令重排序,保證了有序性。
- 對一個
-
線程啟動規則 (Thread Start Rule):
- 對
Thread.start()
的調用 happens-before 啟動的線程中的任何一個操作。 - 重要性: 確保了新啟動的線程能夠看到主線程在調用
start()
之前對共享變量所做的修改。
- 對
-
線程終止規則 (Thread Termination Rule):
- 線程中的所有操作 happens-before 其他線程檢測到這個線程已經終止。(例如,通過
Thread.join()
方法結束、或者Thread.isAlive()
返回false
)。 - 重要性: 確保了在被終止線程結束前對共享變量的修改,在調用
join()
的線程返回后能夠被看到。
- 線程中的所有操作 happens-before 其他線程檢測到這個線程已經終止。(例如,通過
-
線程中斷規則 (Thread Interruption Rule):
- 對線程
interrupt()
方法的調用 happens-before 被中斷線程檢測到中斷事件的發生(例如,Thread.interrupted()
返回true
,或拋出InterruptedException
)。 - 重要性: 保證了中斷操作的可見性。
- 對線程
-
對象終結規則 (Finalizer Rule):
- 一個對象的初始化完成(構造函數執行結束)happens-before 它的
finalize()
方法的開始。 - 重要性: 在對象被垃圾回收器回收并執行
finalize()
方法時,對象的字段已經初始化完畢。
- 一個對象的初始化完成(構造函數執行結束)happens-before 它的
-
傳遞性 (Transitivity):
- 如果操作 A happens-before 操作 B,并且操作 B happens-before 操作 C,那么操作 A happens-before 操作 C。
- 重要性: 這是 happens-before 關系能夠連接和傳遞的關鍵。通過這個規則,我們可以推導出更復雜的并發場景下的可見性保證。
happens-before 原則的意義:
- 程序員的保證: JMM 承諾遵守這些 happens-before 規則,無論底層硬件和操作系統如何實現內存訪問。程序員可以依據這些規則來推理并發程序的正確性,而不必關心底層的復雜細節。
- 同步機制的基礎: Java 中各種同步機制(如
synchronized
,volatile
,final
,Lock
,concurrent
包下的工具類)都是基于這些 happens-before 規則來實現對共享變量的正確訪問。例如,CountDownLatch
的countDown()
happens-beforeawait()
方法成功返回。 - 避免數據競爭: 如果兩個操作分別由不同的線程執行,它們訪問同一個共享變量,其中至少有一個是寫入操作,并且它們之間沒有 happens-before 關系,那么就存在數據競爭 (Data Race)。數據競爭會導致不可預測的結果。編寫并發程序就是要避免數據競爭,確保關鍵操作之間建立 happens-before 關系。
happens-before 原則不是描述實際的時間順序,而是定義了多線程環境下,哪些操作的結果必須對其他哪些操作可見,以及哪些操作的執行順序必須得到保證。