當我們談 Java 併發的時候,你們在談什麼?
前言:
很多人在剛開始學 Java 的時候,會覺得多執行緒是一塊難啃的骨頭,特別是對於非科班的同學。究其原因,我想主要是因為沒有將多執行緒建立起一種模型,不清楚多執行緒的問題到底是怎麼產生的。在這裡,我就和大家聊一下我對Java 多執行緒的一些想法。
Java 是基於 Java 虛擬機器(JVM)實現的一套程式語言,我們寫的 Java 程式碼是要在 JVM 中才能執行。所謂虛擬機器,其實就是模擬了一個作業系統。一個常規作業系統所必備的功能,虛擬機器一般也會有。我們計算機的作業系統能管理記憶體資源,那麼虛擬機器當然也要能管理記憶體資源了。在 JVM 裡,從邏輯的角度來說,會把記憶體劃分為兩部分: 執行緒棧 和 堆 。
嗯,我知道你們對這樣簡單粗暴的劃分方式有意見,JVM 裡面的記憶體劃分遠比上面說的複雜。
而我們今天的談論只涉及到 執行緒棧 (即虛擬機器棧)和 堆 ,因此就簡單地認為 JVM 只劃分了這兩部分。
也就是說,JVM 裡面的記憶體模型,我們可以簡要地畫成下面的那樣:
每一個執行緒對應一個執行緒棧,執行緒棧裡面的資源是私有的,也就是說我們線上程棧裡的變數(即所謂的區域性變數)是不會被多個執行緒共享。
堆記憶體是被所有執行緒所共享的,程式中建立的物件都會保留在堆記憶體中。
好了,說完了 Java 的記憶體模型,我們來看一看計算機的記憶體模型。
我們寫程式碼的時候,程式碼和資料一般都是儲存在硬碟中。當我們在 shell 中輸入完一個 javac命令,或者點選 IDE 的編譯按鈕的時候,我們的程式碼和資料會第一時間複製到記憶體中。複製完成之後會通知我們的 CPU 處理器,然後 CPU 開始執行命令,將記憶體中的資訊複製到 CPU 暫存器中,用來執行相應指令。在現代 CPU 中,CPU 暫存器執行速度非常快,而記憶體執行速度相對來說就非常慢了,為了彌補兩者執行速度之間的巨大差異,在記憶體和 CPU 暫存器之間會有快取記憶體(一般有三級快取),用來暫時存放從記憶體中獲取的資料。整個結構大體如下圖:
上面這幅圖就是計算機的簡單儲存模型,這裡只畫了三層,第一層是 CPU 暫存器,第二層是 CPU 快取記憶體,第三層是記憶體。這裡的箭頭可以理解為資料匯流排,表示資料流動的方向。
在真實計算機中,CPU 快取記憶體一般有多級,其中一部分封裝在 CPU 核中,另一部分封裝在 CPU 處理器中(一個處理器可以有多個核),這裡為了方便,預設都封裝在 CPU 處理器中的。
如果 CPU 想要讀取我們程式碼中的資料,CPU 會先在快取記憶體中查詢需要的資料,如果找到了,那麼就直接使用這資料;如果在快取中沒有找到需要的資料,那麼就會繼續往下找,在記憶體中獲取資料,並且在快取中存放一份,再拿回 CPU 使用。
而 CPU 想要把處理後的資料寫回來的時候,就稍微麻煩一些了。如果 CPU 返回一個資料,就把該資料一級一級地往下送的話,那麼資料匯流排流量就會非常大。因此,什麼時間、以什麼樣的方式將返回的資料寫入下一級儲存器,以達到效能最優,是一個比較困難的問題。我們只知道, CPU 返回一個資料後,我們不會立即在記憶體中看到這個資料 。
瞭解了計算機的記憶體模型,這和 JVM 的記憶體模型有什麼關係呢?
我們已經知道,計算機的記憶體模型和 JVM 的記憶體模型是不一樣的,計算機的記憶體模型裡面並不區分執行緒棧和堆。而 JVM 裡的堆和執行緒棧資訊,一開始也只在計算機的記憶體中,只有當 CPU 執行指令需要堆或執行緒棧中的資訊時,JVM 裡面的一部分堆和執行緒棧的資料才會被載入到快取記憶體和 CPU 暫存器中。因此,JVM 的執行緒棧和堆的資訊可以用下面的圖來表示:
也就是說,JVM 裡面的變數和物件,可能在計算機儲存結構中的任何地方存在。這就會導致兩個問題:
當執行緒更新一個共享變數的值時,會發生記憶體可見性問題(Memory Visibility)。
當多個執行緒對同一個變數進行更新操作時,會產生競態條件(Race Condition)。
這裡其實還可以思考一個問題,即在 JVM 裡面進行的執行緒操作,是如何分佈到作業系統的執行緒的。換句話說,JVM 裡面的執行緒是使用者態還是核心態?
其實 JVM 虛擬機器規範並未對此作出限制,不同的 JVM 可以有不同的實現。HotSpot 虛擬機器預設使用的是核心執行緒,也就是說 HotSpot 虛擬機器不干涉執行緒的排程,全權交由作業系統來處理。當然,如果想將執行緒繫結到特定的 CPU 核執行,也是可以的。HotSpot 虛擬機器中實現了 static bool bind_to_processor(uint processor_id); 方法,用來將執行緒繫結到指定的 CPU 核執行。
在此我向大家推薦一個Java高階群 : 725633148 裡面會分享一些資深架構師錄製的影片錄影:(有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能最佳化、分散式架構、面試資料)等這些成為架構師必備的知識體系 進群馬上免費領取,目前受益良多!
記憶體可見性
假設有一個共享物件,它最開始只是在記憶體中,當一個執行緒爭取到了左 CPU 的時間片,在這段時間裡將共享物件複製到左 CPU 的快取記憶體中,然後左 CPU 對這個共享物件做了一些修改並返回這個共享物件。之前我們說過, CPU 返回一個資料後,我們不會立即在記憶體中看到這個資料,因此,在共享物件的值返回到記憶體之前,如果右 CPU 也想使用這個共享物件,那麼右 CPU 拿到的共享物件不是左 CPU 修改後的共享物件,也就是說右 CPU 得到的共享物件的值不是最新的!
下面透過一副圖來說明這個問題:
在上圖中,左邊的 CPU 會將記憶體中的 obj 物件複製一份在 CPU 快取記憶體中,然後 CPU 對其進行操作,修改了 obj 物件中 count 屬性的值,讓 obj.count 從 1 變成了 2。然而在 CPU 快取記憶體把 obj 最新的值返回到記憶體中之前,右邊的 CPU 執行了相同的程式碼,也從記憶體中獲取了 obj 物件,但它不知道左邊的 CPU 對 obj 物件進行修改了,它 看不見 obj 物件最新的值,因此,右邊的 CPU 獲取的 obj.count 的值還是 1 。
競態條件
可見性問題說的是一個執行緒對共享變數修改了之後,其他執行緒不能立即看到該共享變數最新的值得問題。如果有多個執行緒對同一個變數進行讀取和修改,那麼就可能發生競態條件。
如上圖,假設左邊的 CPU 從記憶體中獲取了 obj 物件,並將其複製到 CPU 快取記憶體中,這個時候,右邊的 CPU 也從記憶體中獲取到了 obj 物件,也將其複製到了 CPU 快取記憶體中。然後兩個 CPU 都對 obj.count 的值增加 1。從整體上來看,obj.count 的值增加了兩次,而當左右兩邊的 CPU 快取記憶體將 obj 的值寫回到記憶體中時,會發現實際上 obj.count 的值只增加了 1 次。
下面的流程圖可以詳細說明這種情況:
左 CPU 和右 CPU 同時爭奪 obj 物件的情況,就被成為“競態條件”。
在此我向大家推薦一個Java高階群 : 725633148 裡面會分享一些資深架構師錄製的影片錄影:(有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能最佳化、分散式架構、面試資料)等這些成為架構師必備的知識體系 進群馬上免費領取,目前受益良多!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545684/viewspace-2168995/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 當我們在談論高併發的時候究竟在談什麼?
- 當我們談論Spring的時候到底在談什麼Spring
- 當我們在談論建構函式注入的時候我們在談論什麼函式
- 當我們在談論VR敘事的時候,我們究竟在談論什麼?VR
- 當我們在談零信任時,我們談的是什麼?
- 當我們談論MOD時,我們在談論什麼?
- 當我們在談論極簡時,我們在談論什麼
- 當我們在談論HTTP快取時我們在談論什麼HTTP快取
- 當我們談優化時,我們談些什麼優化
- 當我們談論格鬥遊戲時,我們在談論什麼遊戲
- 當我們談論Promise時,我們說些什麼Promise
- 當我們談論CloudTable時究竟在談論什麼?Cloud
- HMS Core Insights第三期直播預告—— 當我們在談論App的時候,我們還可以談論什麼?APP
- HMS Core Insights第三期直播回顧 – 當我們在談論App的時候,我們還可以談論什麼?APP
- 當我們談微服務,我們在談什麼 (3) — 如何保障微服務的穩定性微服務
- 當我們談深度學習時,我們用它落地了什麼?深度學習
- 當我們談論版權保護的時候
- 當談PCIe SSD的高效能,我們在談什麼(上)
- 當談PCIe SSD的高效能,我們在談什麼(下)
- 當我談自律的時候,我會談什麼(一)
- 當我們說外掛系統的時候,我們在說什麼
- 當我們談論Virtual DOM時,我們在說什麼——etch原始碼解讀原始碼
- 是時候談談JavaScript物件導向了!(我們什麼時候更需要它)JavaScript物件
- 當我們說開放世界的時候,我們到底在說些什麼?
- 當我們聊kubernetes operator時,我們在聊些什麼
- 當我談 HTTP 時,我談些什麼?HTTP
- 當我談跑酷遊戲時,我在談些什麼遊戲
- 淺談入行Qt桌面端開發程式設計師-從畢業到上崗(1):當我們說到桌面端開發時,我們在談論什麼?QT程式設計師
- 當我們在討論遊戲社群時,我們在討論什麼?遊戲
- 當我們在聊 RN 時,我們在聊什麼 | 技術點評
- 當我們在說“併發、多執行緒”,說的是什麼?執行緒
- 當我們討論TCP的連線運輸管理時,我們在說什麼TCP
- 當提到“事件驅動”時,我們在說什麼?事件
- 當我們說一款遊戲“涼涼”時,我們在說什麼?遊戲
- 當我們的執行 java -jar xxx.jar 的時候底層到底做了什麼?JavaJAR
- 當我們談深度學習時,我們用它落地了什麼?阿里雲內容安全功能全新升級深度學習阿里
- 【iOS】當我們在application:DidFinishLaunchWithOptions:中返回NO時會發生什麼iOSAPP
- 我們究竟在怕什麼?——談談恐怖遊戲的元素堆砌遊戲