更多 Java 并發編程方面的文章,請參見文集《Java 并發編程》
進程 VS 線程
- 進程 Process:資源組織的基本單位
- 線程 Thread:CPU 調度的基本單位,包括程序計數器,寄存器,棧,幀
可見性
單線程程序中的可見性
在單線程程序中,如果首先改變一個變量的值,再讀取該變量的值的時候,所讀取到的值就是上次寫入的值。
也就是說,前面操作的結果對后面的操作是可見的。
多線程程序中的可見性
在多線程程序中,可能不能保證可見性,原因如下:
-
CPU 內部的緩存
CPU 直接操作的是緩存中的數據,并不是直接操作主存。CPU 會在特定的時候,將緩存與主存進行同步。
例如:
CPU 將數據從主存同步到緩存,線程1從緩存中讀取數據并修改,將修改后的數據寫入到緩存,并未同步到主存。而此時線程2從主存中讀取數據,這時線程2讀取到的數據并不是線程1修改后最新的數據。因此這就導致了不可見。 - CPU 可能會改變指令的執行順序,即指令重排
- 處于性能優化的目的,編譯器可能會改變代碼的順序,即代碼重排
在 Java 中:
- 堆內存在線程間共享,存在可見性問題
- 棧內存在線程間不共享,不存在可見性問題
重排序
重排序
Java 內存模型
- Java 內存模型描述了程序中共享變量的關系以及在主存中寫入和讀取這些變量的底層細節。
- 定義了 synchronized,volatile,final 等關鍵字
- Java 開發人員使用這些關鍵字來描述程序所期望的行為
- 編譯器和 JVM 負責保證生成的代碼在運行時的行為符合期望,符合 Java 內存模型的描述
- 定義了 happens - before 的順序
- 如果一個動作按照 happens - before 的順序發生在另一個動作之前,那么前一個動作的結果在多線程的情況下對后一個動作肯定是可見的。
- 例子:對一個對象上的監聽器 monitor 的 解鎖操作 肯定發生在 加鎖操作 之前。
- 例子:對 volatile 變量的 寫操作 肯定發生在 讀操作 之前。
Java 內存模型如下圖所示:
注意: 線程的本地內存是 JVM 的一個抽象概念,并不真實存在。
Java 內存模型
同步
并發編程中需要關注的兩點:
- 線程間的通信:共享內存,消息傳遞
- 線程間的同步:控制不同線程間操作的順序
在共享內存的并發模型中,例如 Java,線程間的同步是顯式的,需要程序員顯示指定。
在消息傳遞的并發模型中,線程間的同步是隱式的,不需要程序員顯示指定,因為消息的發送必須在消息的接收之前。