java內存模型

java併發採用的是共享內存模型,線程之間的通訊對程序員來講是透明的,內存可見性問題很容易困擾着java程序員,今天咱們就來揭開java內存模型的神祕面紗。java

 

在揭開面紗以前,咱們須要認識幾個基礎概念:內存屏障(memory Barriers),指令重排序,happens-before規則,as-if-serial語義。程序員

什麼是 Memory Barrier(內存屏障)?

內存屏障,又稱內存柵欄,是一個CPU指令,基本上它是一條這樣的指令:
一、保證特定操做的執行順序。
二、影響某些數據(或則是某條指令的執行結果)的內存可見性。數組

 

編譯器和CPU可以重排序指令,保證最終相同的結果,嘗試優化性能。插入一條Memory Barrier會告訴編譯器和CPU:無論什麼指令都不能和這條Memory Barrier指令重排序。緩存

 

Memory Barrier所作的另一件事是強制刷出各類CPU cache,如一個 Write-Barrier(寫入屏障)將刷出全部在 Barrier 以前寫入 cache 的數據,所以,任何CPU上的線程都能讀取到這些數據的最新版本。併發

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

這和java有什麼關係?volatile是基於Memory Barrier實現的。app

 

若是一個變量是volatile修飾的,JMM會在寫入這個字段以後插進一個Write-Barrier指令,並在讀這個字段以前插入一個Read-Barrier指令。ide

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

這意味着,若是寫入一個volatile變量a,能夠保證:
一、一個線程寫入變量a後,任何線程訪問該變量都會拿到最新值。
二、在寫入變量a以前的寫入操做,其更新的數據對於其餘線程也是可見的。由於性能

Memory Barrier會刷出cache中的全部先前的寫入。優化

happens-before

從jdk5開始,java使用新的JSR-133內存模型,基於happens-before的概念來闡述操做之間的內存可見性。線程

 

在JMM中,若是一個操做的執行結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在happens-before關係,這個的兩個操做既能夠在同一個線程,也能夠在不一樣的兩個線程中。

 

與程序員密切相關的happens-before規則以下:
一、程序順序規則:一個線程中的每一個操做,happens-before於該線程中任意的後續操做。
二、監視器鎖規則:對一個鎖的解鎖操做,happens-before於隨後對這個鎖的加鎖操做。
三、volatile域規則:對一個volatile域的寫操做,happens-before於任意線程後續對這個volatile域的讀。
四、傳遞性規則:若是 A happens-before B,且 B happens-before C,那麼A happens-before C。

 

注意:兩個操做之間具備happens-before關係,並不意味前一個操做必需要在後一個操做以前執行!僅僅要求前一個操做的執行結果,對於後一個操做是可見的,且前一個操做按順序排在後一個操做以前。

指令重排序

在執行程序時,爲了提升性能,編譯器和處理器會對指令作重排序。可是,JMM確保在不一樣的編譯器和不一樣的處理器平臺之上,經過插入特定類型的Memory Barrier來禁止特定類型的編譯器重排序和處理器重排序,爲上層提供一致的內存可見性保證。

 

一、編譯器優化重排序:編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。
二、指令級並行的重排序:若是不存l在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。
三、內存系統的重排序:處理器使用緩存和讀寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。

 

數據依賴性

若是兩個操做訪問同一個變量,其中一個爲寫操做,此時這兩個操做之間存在數據依賴性。
編譯器和處理器不會改變存在數據依賴性關係的兩個操做的執行順序,即不會重排序。

 

as-if-serial

無論怎麼重排序,單線程下的執行結果不能被改變,編譯器、runtime和處理器都必須遵照as-if-serial語義。

抽象結構

java線程之間的通訊由java內存模型(JMM)控制,JMM決定一個線程對共享變量(實例域、靜態域和數組)的寫入什麼時候對其它線程可見。

 

從抽象的角度來看,JMM定義了線程和主內存Main Memory(堆內存)之間的抽象關係:線程之間的共享變量存儲在主內存中,每一個線程都有本身的本地內存Local Memory(只是一個抽象概念,物理上不存在),存儲了該線程的共享變量副本。

 

因此,線程A和線程B以前須要通訊的話,必須通過一下兩個步驟:一、線程A把本地內存中更新過的共享變量刷新到主內存中。二、線程B到主內存中讀取線程A以前更新過的共享變量。

相關文章
相關標籤/搜索