Java併發(1)- 聊聊Java內存模型

引言

在計算機系統的發展過程當中,因爲CPU的運算速度和計算機存儲速度之間巨大的差距。爲了解決CPU的運算速度和計算機存儲速度之間巨大的差距,設計人員在CPU和計算機存儲之間加入了高速緩存來作爲他們之間的橋樑,在運算時,先將數據拷貝到高速緩存中,計算完成後再將結果寫入計算機存儲,這樣大大提升了計算效率,避免重複屢次訪問計算機存儲形成的cpu資源浪費。html

儘管這樣,CPU仍是存在不少空閒的時間段,爲了壓榨CPU的性能,多任務處理誕生了,同時多任務處理致使任務之間共享資源的爭搶,從而引起了併發問題。java

在Java應用程序中,爲了更好的解決併發問題,就必須深刻理解Java內存模型。Java內存模型是Java虛擬機很是重要的一部分,它用來指導Java虛擬機是如何與計算機硬件內存之間協同工做的。在瞭解Java內存模型以前,咱們先來看看計算機硬件內存模型是怎麼樣的。編程

硬件內存模型

現代計算機一般有2個或以上的CPU,單個CPU可能有多個內核。每一個CPU內核中都包含一組寄存器,CPU在寄存器中執行操做比在計算機主存儲器中快的多。

同時每一個CPU之上還存在高速緩存,但高速緩存的層級和位置是不固定的,現代計算機的緩存層級不少達到了三級,將來可能更多。緩存的位置也各有不一樣,有的集成了部分緩存到CPU中。數組

一樣,緩存的讀寫速度也大大快於計算機主存儲器,在寄存器和主存儲器之間,這樣的CPU--緩存--主存儲的三層結構就構成了硬件內存模型。緩存

CPU在程序的執行過程當中,常常會頻繁的調用相同的數據,好比在一個循環內調用了位於另一個物理地址的函數,這個函數可能與當前指令的物理位置相距甚遠,由於程序使用的物理內存並非連續的,這就致使了須要花費不少沒必要要的時間在物理尋址上。但若是在CPU計算以前會將所須要用到的數據先讀到緩存中,計算完成以後再一次性寫入計算機主存儲器,就能夠避免頻繁訪問計算機主存儲器形成的資源浪費。多線程

Java內存模型

上面說了計算機的硬件內存模型,Java內存模型和硬件內存模型有不少相似的地方。因爲存在不一樣的計算機操做系統類型和硬件類型,致使各類平臺下物理內存模型的不一致。爲了讓Java上層開發有一個統一的內存訪問操做,保證多線程對共享數據的讀寫一致性,JVM規範定義了Java內存模型(Java Memory Model JMM)。 併發

JMM經過happens-before語義(篇幅有限,後面的文章再詳細解說)定義了Java對數據的統一訪問規則。這些數據主要包括實例字段,靜態字段和構成數組的元素,但不包括局部變量、方法參數和異常處理參數,由於局部變量和方法參數是線程私有的,不存在數據競爭問題。引用類型比較特殊,引用自己是線程私有的,但它引用的對象是可被共享的。

JMM還規定了全部的變量都存儲在主內存中(MainMemory),同時每一個線程有本身的本地內存(LocalMeory,也叫工做內存),本地內存中保存了所須要用到的主內存數據的拷貝。線程對變量的讀和寫都在本地內存中進行。app

是否是發現JMM和硬件內存模型存在不少類似之處?主內存對應計算機主存儲,本地內存對應高速緩存。但要知道它們雖然能夠類比,卻並非相同的東西。函數

本地內存僅僅是JMM的一個抽象概念,實際上JVM中並不存在這樣一個區域來對應,這個區域在廣義上能夠包括緩存、寄存器以及其餘的硬件和編譯器優化等等。這句話可能聽起來比較難懂,咱們只須要知道線程對共享變量的操做並不會直接訪問主內存,而是訪問一箇中間層,這個中間層包含了主內存中變量的拷貝,同時中間層的訪問速度大大快於訪問主內存的速度,在必定的操做以後將結果統一寫回主內存,這樣就大大提升了程序的性能。性能

同時也會產生另一個問題,同一個共享變量在每個線程之中都會有一份拷貝(對引用類型,並非拷貝所有數據),產生的線程越多,緩存開銷也就越大。

JVM內存模型

JVM內存模型定義的是線程堆棧和堆之間的內存劃分,它和Java內存模型是有區別的,參照《深刻理解Java虛擬機》中的解釋:

這二者本沒有關係。若是必定要勉強對應,那從變量、主內存、工做內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分,而工做內存則對應於虛擬機棧中的部分區域。從更低層次上說,主內存就是物理內存,而爲了獲取更好的執行速度,虛擬機(甚至是硬件系統自己的優化措施)可能會讓工做內存優先存儲於寄存器和高速緩存中,由於運行時主要訪問——讀寫的是工做內存。

全部的原始類型(boolean,byte,short,char,int,long,float,double)局部變量都存儲在線程堆棧中,不對其餘線程共享。堆中則包含了Java程序中建立的對象。 舉個例子:

public class MemoryModel {
	
	public int i = 0;
	
	public void methodOne() {
		
		int localVarOne = 1;
		
		SharedObject localVarTwo = SharedObject.sharedObject;
		
		Integer localVarThree = new Integer(1);
	}
}

public class SharedObject {
	
	pubic static SharedObject sharedObject = new SharedObject();
	
	public int sharedVarOne = 1;
}
複製代碼

代碼中局部變量localVarOne存儲在線程堆棧中。局部變量localVarTwo的引用存儲在線程堆棧中,但對象自己存儲在堆上。局部變量localVarThree同localVarTwo同樣,引用存儲在線程堆棧中,但對象自己存儲在堆上。不一樣的是多線程執行methodOne方法時,localVarTwo因爲是靜態類型,在堆中只有一份數據,而localVarThree在堆和堆棧中都有多份數據。局部變量對象的成員變量sharedVarOne也存儲在堆上,不管sharedVarOne是基本類型仍是引用類型都是如此。
參考資料:

  1. 《深刻理解Java內存模型》
  2. 《深刻理解Java虛擬機》
  3. 《Java併發編程的藝術》
  4. http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
相關文章
相關標籤/搜索