第12章 Java內存模型與線程
12.1 概述
介紹虛擬機如何實現多線程,多線程之間因爲共享數據而致使的一系列問題及解決方案緩存
12.2 硬件效率與一致性
介紹Java虛擬機內存模型前,先了解下物理機的併發問題。安全
- 硬件效率問題。計算機處理任務除了處理器計算外,還有內存交互,即讀寫數據。而存儲設備與處理機運行速度相差幾個數量級,爲此引入了讀寫速度儘量接近處理器的高速緩存Cache。處理器讀寫緩存數據,緩存將數據同步到內存。
- 緩存一致性問題。在共享內存多核系統中,每一個處理器都有本身的高速緩存,又共享同一主內存。爲了解決一致性問題,處理器訪問高速緩存時,須要遵循一些協議,好比MSI,MESI,MISI,Synapse,Dragon Protocol等。
- 代碼亂序執行優化問題。處理器爲了提升運算效率,會出現不按順序執行的狀況,但單線程下,處理器會保證執行結果與順序執行結果一致。而多線程的狀況下,沒法保證多個任務都按照順序執行。
Java虛擬機有本身的內存模型,也會有與物理機類型的問題。markdown
12.3 Java內存模型
12.3.1 概述
Java內存模型規定:因此變量都存儲在主內存(Main Memory)中,線程有本身的工做內存,工做內存保存變量在主內存副本。線程對變量的讀寫只能再工做內存(Working Memory)中,線程間共享變量須要經過主內存完成。
JVM內存模型的執行處理將圍繞解決兩個問題展開:
複製代碼
- 工做內存數據一致性
- 指令重排序優化,編譯期重排序和運行期重排序。
12.3.2 內存交互操做
主內存與工做內存的交互協議定義以下操做,Java虛擬機必須保證這些操做是原子性的。多線程
- lock,做用於主內存變量,把變量標識爲線程獨佔狀態,使其餘線程沒法lock
- unlock,做用於主內存變量,解除線程獨佔狀態
- read,做用於主內存變量,把變量值傳輸到工做內存中,一邊隨後的load使用
- load,做用於工做內存變量,把read的變量值放入工做內存的變量副本中。
- use,做用於工做內存變量,變量值傳遞給執行引擎
- assign,做用於工做內存變量,執行引擎賦值給工做內存中的變量
- store,做用於工做內存變量,變量值傳輸到主內存,以便後續write使用
- write,做用於主內存變量,把store的變量值放入主內存變量中。
若是要把變量從主內存拷貝到工做內存,必須順序執行 read和load,但不要求必定連續。 若是要把變量從工做內存同步到主內存,必須順序執行 store和write,但不要求必定連續。併發
12.3.3 內存模型運行規則
1.內存交互基本操做的3個特性
Java內存模型是圍繞着在併發過程當中如何處理這3個特性來創建的,歸根結底是爲了實現共享變量在多個工做內存中的一致性,以及併發時,程序能如期運行。app
- 原子性(Atomicity),即一個操做或者多個操做,要麼不執行,要麼所有執行且執行過程不會被打斷
- 可見性(Visibility),當多個線程訪問同一個變量時,一個線程改變了變量值,其餘線程要能當即看到修改過的值。線程經過共享主內存實現可見性。
- 有序性(Ordering),線程內指令串行(as-if-serial),線程間,對於同步(synchrinized)代碼以及volatile字段的操做須要維持相對有序
2.先行發生原則
happens-before優化
- 程序次序規則
- 管程鎖定規則
- volatile變量規則
- 線程啓動規則
- 線程終止規則
- 線程中斷規則
- 對象終結規則
- 傳遞性
3.內存屏障
內存屏障是被插入到兩個CPU指令之間的一種指令,用來禁止處理器指令發生指令重排序。spa
12.3.4 volatile型變量
volatile主要有下面兩種語義線程
語義1 保證可見性
保證了不一樣線程對該volatile型變量操做的內存可見性,但不等同於併發操做的安全性code
- 線程寫volatile變量的過程assign-store-write必須連續出現:
- 改變工做內存中volatile變量副本的值
- 將改變的副本值刷新到主內存中
- 線程讀volatile變量的過程read-load-use必須連續出現:
- 從主內存讀取volatile變量值並存入工做線程副本
- 從工做內存讀取變量副本
語義2 禁止指令重排序
volatile型變量使用場景總結起來就是"一次寫入,處處讀取",某個線程負責更新變量,其餘線程只讀取變量,並根據變量新值執行相應邏輯,例如狀態標誌位更新,觀察者模型變量值發佈