淺談Java記憶體模型

来源:https://www.cnblogs.com/CodeBear/archive/2018/12/16/10129159.html
-Advertisement-
Play Games

Java記憶體模型雖說是一個老生常談的問題 ,也是大廠面試中繞不過的,甚至初級面試也會問到。但是真正要理解起來,還是相當困難,主要這個東西看不見,摸不著。網上已經有大量的博客,但是人家的終究是人家的,自己也要好好的去理解,去消化。今天我也來班門弄斧,說下Java記憶體模型。 說到Java記憶體模型,不得不 ...


Java記憶體模型雖說是一個老生常談的問題 ,也是大廠面試中繞不過的,甚至初級面試也會問到。但是真正要理解起來,還是相當困難,主要這個東西看不見,摸不著。網上已經有大量的博客,但是人家的終究是人家的,自己也要好好的去理解,去消化。今天我也來班門弄斧,說下Java記憶體模型。

說到Java記憶體模型,不得不說到 電腦硬體方面的知識。

電腦硬體體系

我們都知道CPU 和 記憶體是電腦中比較核心的兩個東西,它們之間會頻繁的交互,隨著CPU發展越來越快,記憶體的讀寫的速度遠遠不如CPU的處理速度,所以CPU廠商在CPU上加了一個 高速緩存,用來緩解這種問題。我們在看CPU硬體參數的時候,也會看到有這樣的參數:
image.png
一般高速緩存有3級:L1,L2,L3,CPU與記憶體的交互,就發生了變化,CPU不再與記憶體直接交互,CPU會先去L1中尋找數據,沒有的話,再去L2中尋找,然後是L3,最後才去記憶體尋找(更準確的來說,應該是CPU中的寄存器去尋找)。

我們可以畫一張圖來理解:
image.png

看起來一切都很美好,但是隨著科技的進步,CPU廠商們叒搞事了,推出了多核CPU,每個CPU上又有高速緩存,CPU與記憶體的交互就變成了下麵這個樣子:
image.png

這樣就會引發一個問題:緩存不一致

為什麼會出現這個問題呢?

CPU需要修改某個數據,是先去Cache中找,如果Cache中沒有找到,會去記憶體中找,然後把數據複製到Cache中,下次就不需要再去記憶體中尋找了,然後進行修改操作。而修改操作的過程是這樣的:在Cache裡面修改數據,然後再把數據刷新到主記憶體。其他CPU需要讀取數據,也是先去Cache中去尋找,如果找到了就不會去記憶體找了。

所以當兩個CPU的Cache同時都擁有某個數據,其中一個CPU修改了數據,另外一個CPU是無感知的,並不知道這個數據已經不是最新的了,它要讀取數據還是從自己的Cache中讀取,這樣就導致了“緩存不一致”。

其實對於這樣的描述並不是十分準確,因為計算、讀取等操作都是在CPU的寄存器中進行的,這樣的描述是為了讓問題變得更簡單,相信學過電腦體系的人應該非常清楚整個流程,在這裡就簡單的描述下。

解決這個問題的方法有很多,比如:

  • 匯流排加鎖(此方法性能較低,現在已經不會再使用)
  • MESI協議
    這是Intel提出的,MESI協議也是相當複雜,在這裡我就簡單的說下:當一個CPU修改了Cache中的數據,會通知其他緩存了這個數據的CPU,其他CPU會把Cache中這份數據的Cache Line置為無效,要讀取數據的話,直接去記憶體中獲取,不會再從Cache中獲取了。

當然還有其他的解決方案,MESI協議是其中比較出名的。

Java線程與硬體處理器

其實,我們在Java中開啟一個線程,最終Java也會交給CPU去執行。
具體的流程是:我們在使用Java線程,內部會調用操作系統(OS)的內核線程(Kernel-Level Thread),這種線程是操作系統內核(Kernel)直接支持的,內核通過調度器,對線程進行調度,並將線程交給各個CPU內核去處理。

如下圖所示:
image.png

Java記憶體模型

看到標題,大家肯定會想:我靠,難道上面說的都和Java記憶體模型沒有關係嗎,從這裡才是真正介紹Java記憶體模型嗎?其實,並不是,Java記憶體模型是一個抽象的概念,其實並不存在,它描述的是一種規範,最終Java程式都會交給CPU去運行,所以上面是電腦硬體體系是基礎,有了上面的基礎,才有了Java記憶體模型,或者說Java的記憶體模型就是利用了電腦硬體體系。

還是從一張圖來入手:
image.png

本地記憶體:存放的是 私有變數 和 主記憶體數據的副本。如果私有變數是基本數據類型,則直接存放在本地記憶體,如果是引用類型變數,存放的是引用(指針),實際的數據存放在主記憶體。本地記憶體是不共用的,只有屬於它的線程可以訪問。也有好多人把 本地記憶體 稱之為 線程棧 或者 工作空間。

主記憶體:存放的是共用的數據,所有線程都可以訪問。當然它也有不少其他稱呼,比如 堆記憶體,共用記憶體等等。

Java記憶體模型規定了所有對共用變數的讀寫操作都必須在本地記憶體中進行,需要先從主記憶體中拿到數據,複製到本地記憶體,然後在本地記憶體中對數據進行修改,再刷新回主記憶體。

通過前面的鋪墊,我們應該認識到Java的執行最終還是會交給CPU去處理,但是Java的記憶體模型和硬體架構又不完全一致。對於硬體來說,只有CPU,Cache和主記憶體,並沒有Java記憶體模型中本地記憶體(線程棧、工作空間)或者主記憶體(共用記憶體,堆記憶體)的概念,所以不管是Java記憶體模型中的本地記憶體,還是主記憶體的數據,最終都會存儲在CPU(更準確的來說 是寄存器)、Cache、記憶體上。

所以,Java記憶體模型和電腦硬體架構存在這樣的關係:

image.png

Java記憶體模型就是為瞭解決多線程對共用數據的讀寫一致性問題。

併發編程中三個重要特性

原子性:

不可分割,同生共死。
i=1
具有原子性,直接 在本地記憶體中進行賦值操作。

i++;
不具有原子性,有三個步驟
1.把i讀取出來(原子性)
2.在本地記憶體中做自增運算(原子性)
3.再把值寫回i(原子性)

多個原子性操作組合在一起,就不具有原子性了。

一般情況下,基本數據類型的賦值,讀取都是具有原子性的。

可見性

一個線程在本地記憶體中修改了共用記憶體的數據,對於其他持有該數據的線程是“不可見”的。

有序性

代碼在運行的時候,執行順序可能並不是嚴格從上到下執行的,會進行指令重排。
根據CPU流水線作業,一般來說 簡單的操作會先執行,複雜的操作後執行。
指令重排會有兩個規則:

  • as-if-seria
    不管怎麼重排序,單線程的執行結果不能發生改變。正是由於這個特性,在單線程中,程式員一般無需理會重排序帶來的問題。
  • happens-before
    1. 程式次序規則
      一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作。
    2. volatile規則(以後會花一整節內容介紹,這裡不展開)
    3. 鎖定規則
      如果鎖處於Lock的狀態,必須等Unlock後,才能再次進行Lock操作。
    4. 傳遞規則
      A happens-before B , B happens-before C,那麼A happens-before C。

Java記憶體模型是個相當複雜的東西,我在這裡可能還說不上是談,只能說是“蜻蜓點水 ”般的介紹下。希望通過這篇文章,大家可以對Java模型有一個初步的瞭解。

以後,我也會介紹Synchronized 和 volatile關鍵字等等,我可能會再次提到本節中涵蓋的內容,並做進一步的補充說明。

好了,本文的內容到這裡就結束了,在寫之前,已經做好心理準備了,可能需要花上半天時間,但是實際上遠遠不止半天,在寫的過程中,翻閱了大量的文章,包括 知乎、博客園、簡書 等等,發現 如果要“較真”“抬杠”的話,文章與文章之間也有有衝突的地方,甚至一篇文章中,也有前後矛盾的地方。我也不奢求本文中介紹的所有內容都是正確的。為了不誤人子弟,如果大家發現有錯誤,希望可以及時向我提出,我也會儘快核實後修改。

感謝大家可以看到最後,再見。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • pygame製作"停不下來的奧爾加團長"小游戲 一、pygame簡介 Pygame 是一組用來開發游戲軟體的 Python 程式模塊,基於 SDL 庫的基礎上開發。允許你在 Python 程式中創建功能豐富的游戲和多媒體程式,Pygame 是一個高可移植性的模塊可以支持多個操作系統。用它來開發小游戲 ...
  • 工廠模式 一:簡單工廠模式 1. 問題的引出 我們打算做一個製作pizza的系統,從訂購到出貨,初始代碼如下: 客戶端通過調用pizza類的orderPizza方法來創建pizza,根據type的不同來獲取不同種類的pizza,然而以上的設計存在著很多問題: 1. Pizza類中存在大量的if el ...
  • 《設計模式:可復用面向對象軟體基礎》 這本書還沒看完,但是絕對是案頭必備,雖然用C++寫的代碼,並且是四個牛人寫的風格(相關知識背景與程式員不同),但是一旦開始理解設計模式以後,再回過頭會發現這本書的定義和描述最容易理解和記憶。 《Head First設計模式》 自學、初學的推薦用書。我也是通過這本 ...
  • 原文鏈接:SAP ABAP7.50系列之預定義數據結構 公眾號:SAP Technical 前言部分 先說一下,之前有些文章被轉載之後也沒有註明,這個就比較不好。如果你覺得本文寫的並不好,那麼可以直接去看HELP,這樣更直接,我這裡只是做記錄,如果讀者朋友感興趣,可以關註公眾號,也可以在本文末留言, ...
  • 2.帶getter和setter屬性 3.對象私有欄位 在Scala中,方法可以訪問該類的所有對象的私有欄位 4.Bean屬性 當你將Scala欄位標註為@BeanProperty時,會自動生成四個方法 5.輔助構造器 6.主構造器 7.嵌套類 ...
  • 你們可能會想,棧長這麼菜的嗎?5分鐘都堅持不了? 本文說起來會有點尷尬,畢竟這是棧長我曾經經歷過的故事。。。 那時候的棧長還真菜,每天寫著 if/ for 及一些簡單的業務邏輯代碼,雖工作有些日子了,但技術水平還停留在剛畢業的起步階段。。。 記得,那是一個周末,棧長去某知名互聯網公司面試,好像不到五 ...
  • 超時 網路請求不可避免會遇上請求超時的情況,在 requests 中,如果不設置你的程式可能會永遠失去響應。超時又可分為連接超時和讀取超時。 連接超時 連接超時指的是在你的客戶端實現到遠端機器埠的連接時(對應的是connect()),Request 等待的秒數。 import timeimport ...
  • 本文介紹python中的while迴圈、for迴圈。在python中for可以用於迴圈,也可用於另一種近親的列表解析,列表解析是python中非常重要的特性,詳細內容見後面的文章。 一般來說,python寫for迴圈比寫while更容易、方便,而且python中的for比while效率要更高,如果可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...