嘿,同學,你要的Java內存模型(JMM)來了

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、 計算機的硬件內存結構","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、 Java內存模型的背景和定義","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、 Java內存模型3.1 主內存、工作內存的定義3.2 內存的交互操作3.3 JMM緩存不一致問題","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、 Java內存模型的實現","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在學習Java內存模型(JMM)前,我們先了解下計算機的硬件內存結構,因爲JMM結構就是基於此演變而來的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"1、 計算機的硬件內存結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在單核計算機中,計算機中的CPU計算速度是非常快的,但是與計算機中的其它硬件(如IO、內存等)同CPU的速度比起來是相差甚遠的,所以協調CPU和各個硬件之間的速度差異是非常重要的,要不然CPU就一直在等待,浪費資源。單核尚且如此,在多核中,這樣的問題會更加的突出。硬件結構如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f4/f486263d6ba6af582f5a5099a6f32ae4.png","alt":"image-20210129225817989","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先大概梳理下這個流程:當我們的計算機要執行某個任務或者計算某個數字時,主內存會首先從數據庫中加載計算機計算所需要的數據,因爲內存和CPU的速度相差較大,所以有必要在內存和CPU間引入緩存(根據實際的需要,可以引入多層緩存),主內存中的數據會先存放在CPU緩存中,當這些數據需要同CPU做交互時會加入到CPU寄存器中,最後被CPU使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,在單核情況下,基於緩存的交互可以很好的解決CPU與其它硬件之間的速度匹配,但是在多核情況下,各個處理器都要遵循一定的協議來保障內存中的各個處理器的緩存和主內存中的數據一致性問題,這類協議通常被稱爲緩存一致性協議。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/32/328678bebda3e5ee1c9a33893ae3177e.png","alt":"image-20210129232410774","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"2、 Java內存模型的背景和定義","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在開發時會經常遇到這樣的場景,我們開發完成的代碼在我們自己的運行環境上表現良好,但是當我們把它放在其它硬件平臺上時,就會出現各種各樣的錯誤,這是因爲在不同的硬件生產商和不同的操作系統下,內存的訪問邏輯有一定的差異,結果就是當你的代碼在某個系統環境下運行良好,並且線程安全,但是換了個系統就出現各種問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這個問題,Java內存模型(JMM)的概念就被提出來了,它的出現可以屏蔽系統和硬件的差異,讓一套代碼在不同平臺下能到達相同的訪問結果,實現平臺的一致性,使得Java程序能夠","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一次編寫,到處運行","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣的描述的好像有點熟悉啊,這不是JVM的概念描述麼,它們兩者有什麼區別啊?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JVM與JMM間的區別?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上,JMM是Java虛擬機(JVM)在計算機內存(RAM)中的工作方式,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本,本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化。而JVM則是描述的是Java虛擬機內部及各個結構間的關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小夥伴這時可能會有疑問,既然JMM是定義線程和主內存之間的關係,那麼它的出現是不是解決併發領域的問題啊?沒錯,我們先回顧一下併發領域中的關鍵問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"併發領域中的關鍵問題?","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程之間的通信","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在編程中,線程之間的通信機制有兩種,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"共享內存","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"消息傳遞","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"在共享內存的併發模型裏,線程之間共享程序的公共狀態,線程之間通過寫-讀內存中的公共狀態來隱式進行通信,典型的共享內存通信方式就是通過共享對象進行通信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"消息傳遞的併發模型裏,線程之間沒有公共狀態,線程之間必須通過明確的發送消息來顯式進行通信,在java中典型的消息傳遞方式就是wait()和notify()。","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程間的同步","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"同步是指程序用於控制不同線程之間操作發生相對順序的機制。在共享內存併發模型裏,同步是顯式進行的。程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執行。在消息傳遞的併發模型裏,由於消息的發送必須在消息的接收之前,因此同步是隱式進行的。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,Java內存模型(JMM)的併發採用的是共享內存模型。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面,我們一起來學習Java內存模型","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"3、 Java內存模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先看一張JMM的控制模型作圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3b/3be664e6b9167e361954a15cd1d8edf3.png","alt":"image-20210201190128153","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此可見,Java內存模型(JMM)同CPU緩存模型結構類似,是基於CPU緩存模型來建立的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先梳理一下JMM的工作流程,以上圖爲例,我們假設有一臺四核的計算機,cpu1操作線程A,cpu2操作線程B,cpu3操作線程C,當這三個線程都需要對主內存中的共享變量進行操作時,這三條線程分別會將主內存中的共享內存讀入自己的工作內存,自己保存一份共享變量的副本供自己線程本身使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時有的小夥伴可能會有以下疑問:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主內存、工作內存的定義是什麼?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如何將主內存中的共享變量讀入自己線程本身的工作內存?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當其中的某一條線程修改了共享變量後,其餘線程中的共享變量值是否變化,如果變化,線程間是怎麼保持可見性的?","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面,我們針對這兩個疑問一一解答。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"3.1 主內存、工作內存的定義","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主內存","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"主內存主要存儲的是Java實例對象,即所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量),當然也包括了共享的類信息、常量、靜態變量。由於是共享數據區域,多條線程對同一個變量進行訪問可能會發現線程安全問題。","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工作內存","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"工作內存主要存儲當前方法的所有本地變量信息(工作內存中存儲着主內存中的變量副本拷貝),即每個線程只能訪問自己的工作內存,即線程中的本地變量對其它線程是不可見的,就算是兩個線程執行的是同一段代碼,它們也會各自在自己的工作內存中創建屬於當前線程的本地變量,當然也包括了字節碼行號指示器、相關Native方法的信息。注意由於工作內存是每個線程的私有數據,線程間無法相互訪問工作內存,因此存儲在工作內存的數據不存在線程安全問題。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"NOTE","attrs":{}},{"type":"text","text":":這裏的主內存、工作內存與Java內存區域中的Java堆、棧、方法區不是同一層次的內存劃分,這兩者基本上沒有關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"搞清楚主內存和工作內存後,下一步就需要學習主內存與工作內存的數據交互操作的方式。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"3.2 內存的交互操作","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主內存與工作內存的交互操作有8種,虛擬機必須保證每一個操作都是原子的,這八種操作分別是:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Lock(鎖定)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"unlock(解鎖)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"read(讀取)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"load(載入)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於工作內存的變量,它把read操作從主存中變量放入工作內存中","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"use(使用)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"assign(賦值)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"store(存儲)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便後續的write使用","attrs":{}}]}],"attrs":{}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"write(寫入)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"作用於主內存中的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單看這八種類型的原子操作可能有點抽象,我們畫一個操作流程圖仔細梳理下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"操作流程圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f3c276f2add5ee43672cae54e99398ff.png","alt":"image-20210201222919562","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可以看出,如果要把一個變量從內存中複製到工作內存中,就需要順序的執行read和load操作,如果把變量從工作內存同步到主內存中,就需要執行store和write操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"NOTE:","attrs":{}},{"type":"text","text":" Java內存模型只要求上述操作必須按順序執行,卻沒要求是連續執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以兩個線程爲例梳理下操作流程:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"假設存在兩個線程A和B,如果線程A要與線程B要通信的話,首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去;然後,線程B到主內存中讀取線程A之前已經更新過的共享變量。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"敏銳的小夥伴可能會發現,如果多個線程同時讀取修改同一個共享變量,這種情況可能會導致每個線程中的本地內存中緩存變量一致的問題,這個時候該怎麼解決呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"3.3 JMM緩存不一致問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解決JMM中的本地內存變量的緩存不一致問題有兩種解決方案,分別是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"總線加鎖","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MESI緩存一致性協議","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"總線加鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總線加鎖是CPU從主內存讀取數據到本地內存時,會先在總線對這個數據加鎖,這樣其它CPU就沒法去讀或者去寫這個數據,直到這個CPU使用完數據釋放鎖後,,其它的CPU才能讀取該數據。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/340ebc442de393e5d6eada74e0f5cea6.png","alt":"image-20210201225909928","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總線加鎖雖然能保證數據一致,但是它卻嚴重降低了系統性能,因爲當一個線程多總線加鎖後,其它線程都只能等待,將原有的並行操作轉成了串行操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常情況下,我們不採用這種方法,而是使用性能較高的緩存一致性協議。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"MESI緩存一致性協議","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"MESI緩存一致性協議是多個CPU從主內存讀取同一個數據到各自的高速緩存中,當其中的某個CPU修改了緩存裏的數據,該數據會馬上同步回主內存,其它CPU通過總線嗅探機制可以感知到數據的變化從而將自己緩存裏的數據失效。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在併發編程中,如果多個線程對同一個共享變量進行操作是,我們通常會在變量名稱前加上關鍵在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":",因爲它可以保證線程對變量的修改的可見性,保證可見性的基礎是多個線程都會監聽總線。即當一個線程修改了共享變量後,該變量會立馬同步到主內存,其餘線程監聽到數據變化後會使得自己緩存的原數據失效,並觸發","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"操作讀取新修改的變量的值。進而保證了多個線程的數據一致性。事實上,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"的工作原理就是依賴於MESI緩存一致性協議實現的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#135ce0","name":"user"}}],"text":"4、 Java內存模型的實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java多線程中,Java提供了一系列與併發處理相關的關鍵字,比如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"synchronized","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"final","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"concurren","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"包等。其實這些就是Java內存模型封裝了底層的實現後提供給程序員使用的一些關鍵字","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,Java內存模型的本質是圍繞着Java併發過程中的如何處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"原子性","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"可見性","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"順序性","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#1394d8","name":"user"}}],"attrs":{}},{"type":"text","text":"這三個特徵來設計的,這三大特性可以直接使用Java中提供的關鍵字實現,它們也是面試中經常被問到的題目。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"原子性","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"原子性的定義是一個操作不能被打斷,要麼全部執行完畢,要麼不執行。在這點上有點類似於事務操作,要麼全部執行成功,要麼回退到執行該操作之前的狀態。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JMM保證的原子性變量操作包括read、load、assign、use、store、write","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"NOTE","attrs":{}},{"type":"text","text":":基本類型數據的訪問大都是原子操作,long 和double類型的變量是64位,但是在32位JVM中,32位的JVM會將64位數據的讀寫操作分爲2次32位的讀寫操作來進行,這就導致了long、double類型的變量在32位虛擬機中是非原子操作,數據有可能會被破壞,也就意味着多個線程在併發訪問的時候是線程非安全的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於非原子操作的基本類型,可以使用synchronized來保證方法和代碼塊內的操作是原子性的。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"synchronized (this) {\n a=1; \n b=2;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如一個線程觀察另外一個線程執行上面的代碼,只能看到a、b都被賦值成功結果,或者a、b都尚未被賦值的結果。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"可見性","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java內存模型是通過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存作爲傳遞媒介的方式來實現的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後可以立即同步到主內存,被其修飾的變量在每次是用之前都從主內存刷新。因此,可以使用volatile來保證多線程操作時變量的可見性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了volatile,Java中的synchronized和final兩個關鍵字也可以實現可見性。只不過實現方式不同,這裏不再展開了。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"有序性","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java中,可以使用synchronized和volatile來保證多線程之間操作的有序性。實現方式有所區別:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"bgcolor","attrs":{"color":"#fff9f9","name":"user"}}],"text":"volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條線程操作。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,這裏簡單的介紹完了Java併發編程中解決原子性、可見性以及有序性可以使用的關鍵字。讀者可能發現了,好像synchronized關鍵字是萬 能的,他可以同時滿足以上三種特性,這其實也是很多人濫用synchronized的原因。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是synchronized是比較影響性能的,雖然編譯器提供了很多鎖優化技術,但是也不建議過度使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"參考文獻","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[1]https://www.jianshu.com/p/8a58d8335270","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[2]https://blog.csdn.net/javazejian/article/details/72772461","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[3]https://blog.csdn.net/zjcjava/article/details/78406330","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[4]https://segmentfault.com/a/1190000016085105","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章