Java併發_01_JMM_02_內存模型

一、 前言

1. 關於CPU緩存

  • 現代計算機都可支持多任務處理,因爲如果只讓CPU去單任務處理的話,由於CPU的運算速度和它的存儲和通信子系統速度差距太大,大多的時間都浪費在磁盤I/O、網絡通信或者數據庫訪問上,這讓計算機的性能大大降低。
  • 引入 多任務,則可以讓CPU同時去處理多個任務,在一個任務進行I/O時,大可不必理會它,則去執行其他任務。
  • 其次現代計算機都在硬件上添加一層讀寫速度借鑑處理器運算素的的 高速緩存 (Cache)來作爲內存和處理器之間的緩存,將運算需要的數據複製一份到緩衝中。
  • 帶來的問題
    • 多個處理器 一起協同工作時,各自的緩衝區數據如何保證一致?
    • 這時就需要,各個處理器在訪問緩存時,都遵循 緩存一致性協議, 每個處理器通過嗅探在總線上傳播的數據來檢查自己的緩存是否過期,當處理器發現自己緩存過期,就會將當前處理器的緩存行設置爲無效狀態,在處理器要對這個數據進行操作時,會強制重新從系統內存中把數據讀到處理器緩存裏。
      處理器緩存

2. 關於Java的內存模型

  • Java內存模型如同處理器的緩存一樣
  • JMM規定了所有的變量都應存儲主存當中,每個線程都有自己的工作內存
  • 線程對變量的所有操作都是在自己的工作內存中進行的,而不能直接對主存進行操作,並且每個線程不能訪問其它線程的工作內存。
  • Java線程之間的通信由 JMM 通過使用lock/unlock/read/load/use/assign/store/write 操作來進行控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見
    JMM

二、原子性、可見性、有序性

  • 對於併發而言,要想正確的執行程序,必須保證 原子性可見性有序性只要有一個沒有被保證,就可能會導致 程序運行不正確。

1. 原子性

  • 一個或者多個操作要麼全部執行且執行過程不可被任何因素打斷,要麼就不執行。
  • 可以通過 synchronize 、 Lock 或者 CAS 來實現 原子性
  • JMM提供了 8 種原子性變量的操作:( luck、unlock )、( read、load )、( assign、use )、(store、write )
    • 其中兩兩一對使用,有前一個操作必須有後一個操作。
    • 其中 luck 和 unlock 操作也能保證原子性,但是虛擬機沒有將這兩個操作暴露給用戶,但是卻提供了更高層次的 monitorenter 和 monitorexit 來隱式的使用這兩個操作。
  • 在Java中,只有對 除 long 和 double 外的基本類型進項簡單的賦值(如 int a = 1 ; int a = b;不是原子性的,因爲要讀取b的值)或讀取操作,纔是原子的。
    • 如果JVM爲32位的,則JVM不保證64位的long和double型 的操作的原子性
      • 在JDK1.5之前,對 long 和 double 的寫操作和讀操作都不保證是原子性的。
      • 在JDK1.5之後,保證了讀操作的原子性,但是寫操作還是分爲2步去寫。
    • 64位的JVM,long 和 double 讀寫都是原子操作。
    • 要解決 long 和 double 的非原子性操作,可以在類型前加上 volatile ,操作就是原子性的了。
      • JVM 不保證 long 和double在 32位機下 寫操作的原子性,但是可以保證 volatile 修飾的 long 和double 寫操作的原子性。
  • Java 提供了13中原子類:https://blog.csdn.net/qq_39541319/article/details/89851725

2. 可見性

  • 可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量,那麼其他線程能夠立即得知這個修改。
  • 可以通過 synchronized 、Lock 、final 或者 volatile 來實現。

3. 有序性

  • 有序性是指程序指向的順序按照代碼的先後順序執行。
  • 可以使用 volatile 和 synchronized 實現有序性
  • volatile 實現有序性是 volatile 通過插入內存屏障的方式禁止指令重排序
  • synchronized 是通過鎖實現線程的互斥訪問,使得兩個同步塊只能串行地進入(注意:synchronized 修飾的同步塊臨界區中還是會進行指令重排序,由於同步塊裏面是互斥訪問的,所以臨界區中的指令重排序不會影響)

三、重排序

在執行程序的時候,爲了提高並行度,編譯器和處理器會對指令進行重排序。

並行度: 指令或數據並行執行的最大數目。在指令流水中,同時執行多條指令稱爲指令並行。

1. 重排序的種類及重排序條件

(加粗部分爲重排序的條件)

  • 編譯器重排序(編譯器優化重排序):編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序;
  • 處理器重排序(指令級並行的重排序,內存系統的重排序):如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序;

2. 帶來的問題

  • 重排序在多線程環境下可能導致數據不安全
  • 解決

3. 應用:DCL 單例模式:

https://blog.csdn.net/qq_39541319/article/details/89851821

四、順序一致性

  • 一個線程中所有的操作必須按照程序的順序來執行;
  • 不管程序是否同步,所有線程都只能看到一個單一的執行順序。
  • 每個操作都必須原子執行且立即對所有線程可見
  • JMM不保證未同步的程序的執行結果與該程序順序一致性模型中的執行結果相同。

五、as-if-serial

  • 編譯器和處理器不管對代碼怎麼進行重排序沒應保證代碼在單線程下執行結果是不變的。
  • 我們理解的一段程序代碼的執行在單個線程中看起來是有序的,順序執行的。那是因爲單線程中無法觀測到重排序帶來的問題。
  • 事實上,這個規則是用來保證程序在單線程中執行結果的正確性,但是無法保證程序在多線程執行結果的正確性。

六、happens-before

  • JMM中,如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。
  • 如果一個操作 happens-before 另外一個操作,那麼第一個操作的直系那個結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前
  • 兩個操作制之間存在 happens-before 關係,並不意味着一定要按照 happens-before原則的順序來執行,如果重排序之後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。

1. 理解

  • happens-before 是JMM最核心的理論,保證內存可見性。
  • JMM在設計時,一方面爲程序員提供足夠強的內存可見性保證,另一方面,對編譯器和處理器的限制要儘可能放鬆。
  • 通過happens-before 關係,不允許影響程序執行結果發生改變的重排序。
  • 只要不改變程序的直系那個結果(指的是單線程程序 和 正確同步的多線程程序),編譯器和處理器怎麼優化都可以。
  • as-if-serial 語義保證單線程內程序的執行結果不被改變,happens-before關係保證正確同步的多線程程序的執行結果不被改變。
  • as-if-serial 語義給編寫單線程程序的程序員創造了一個幻境:單線程程序是程序的順序來執行的。
  • happens-before 關係給編寫正確同步的多線程程序的程序員創建一個幻境:正確同步的多線程程序是按happens-before指定的順序來執行的。

思維導圖

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章