Java內存模型-(1)

在介紹Java內存模型以前,先來看下計算機的內存模型,而後再來看Java內存模型在計算機的內存模型基礎上作了哪些事情。程序員

要說計算機的內存模型,首先來看下爲什麼要有內存模型?編程

1、爲何要有內存模型

首先,「內存模型」是一個與計算機硬件有關的概念,先來看下有什麼關係?緩存

CPU和緩存一致性

  1. 內存速度知足不了CPU的讀寫速度:計算機在執行程序的時候,每條指令都是在CPU中執行的,且免不了要和數據打交道,而計算機上面的數據,是存放在主存當中的,也就是計算機的物理內存啦。剛開始,還相安無事的,可是隨着CPU技術的發展,CPU的執行速度愈來愈快。而因爲內存的技術並無太大的變化,因此從內存中讀取和寫入數據的過程和CPU的執行速度比起來差距就會愈來愈大,這就致使CPU每次操做內存都要耗費不少等待時間。安全

  2. CPU和內存之間增長高速緩存:但是,不能由於內存的讀寫速度慢,就不發展CPU技術,總不能讓內存成爲計算機處理的瓶頸吧。因此,人們想出來了一個好的辦法,就是在CPU和內存之間增長高速緩存。緩存的概念你們都知道,就是保存一份數據拷貝。他的特色是速度快,內存小,而且昂貴。那麼,程序的執行過程就變成了:當程序在運行過程當中,會將運算須要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束以後,再將高速緩存中的數據刷新到主存當中。多線程

  3. 衍生出多級緩存:而隨着CPU能力的不斷提高,一層緩存就慢慢的沒法知足要求了,就逐漸的衍生出多級緩存。按照數據讀取順序和與CPU結合的緊密程度,CPU緩存能夠分爲一級緩存(L1),二級緩存(L3),部分高端CPU還具備三級緩存(L3),每一級緩存中所儲存的所有數據都是下一級緩存的一部分。這三種緩存的技術難度和制形成本是相對遞減的,因此其容量也是相對遞增的。那麼,在有了多級緩存以後,程序的執行就變成了:當CPU要讀取一個數據時,首先從一級緩存中查找,若是沒有找到再從二級緩存中查找,若是仍是沒有就從三級緩存或內存中查找。併發

  4. 多線程、多CPU出現問題:單核CPU只含有一套L1,L2,L3緩存;若是CPU含有多個核心,即多核CPU,則每一個核心都含有一套L1(甚至和L2)緩存,而共享L3(或者和L2)緩存。編程語言

    單線程、單CPU核心性能

    cpu核心的緩存只被一個線程訪問。緩存獨佔,不會出現訪問衝突等問題。優化

    多線程、單CPU核心操作系統

    進程中的多個線程會同時訪問進程中的共享數據,CPU將某塊內存加載到緩存後,不一樣線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即便發生線程的切換,緩存仍然不會失效。但因爲任什麼時候刻只能有一個線程在執行,所以不會出現緩存訪問衝突。

    多線程、多CPU核心

    每一個核都至少有一個L1 緩存。多個線程訪問進程中的某個共享內存,且這多個線程分別在不一樣的核心上執行,則每一個核心都會在各自的caehe中保留一份共享內存的緩衝。因爲多核是能夠並行的,可能會出現多個線程同時寫各自的緩存的狀況,而各自的cache之間的數據就有可能不一樣。

    在CPU和主存之間增長緩存,在多線程場景下就可能存在緩存一致性問題,也就是說,在多核CPU中,每一個核的本身的緩存中,關於同一個數據的緩存內容可能不一致。

處理器優化和指令重排

上面提到在在CPU和主存之間增長緩存,在多線程場景下會存在緩存一致性問題。除了這種狀況,還有一種硬件問題也比較重要。那就是爲了使處理器內部的運算單元可以儘可能的被充分利用,處理器可能會對輸入代碼進行亂序執行處理。這就是處理器優化。除了如今不少流行的處理器會對代碼進行優化亂序處理,不少編程語言的編譯器也會有相似的優化,好比Java虛擬機的即時編譯器(JIT)也會作指令重排

可想而知,若是任由處理器優化和編譯器對指令重排的話,就可能致使各類各樣的問題。

2、併發編程的問題

併發編程存在的問題:原子性問題、可見性問題、有序性問題。這是人們抽象定義出來的。而這個抽象的底層問題就是前面提到的緩存一致性問題、處理器優化問題和指令重排問題等。

併發編程,爲了保證數據的安全,須要知足如下三個特性:

原子性:指在一個操做中就是cpu不能夠在中途暫停而後再調度,既不被中斷操做,要不執行完成,要不就不執行

可見性:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。

有序性:程序執行的順序按照代碼的前後順序執行。

不難發現,緩存一致性問題其實就是可見性問題。而處理器優化是能夠致使原子性問題的。指令重排即會致使有序性問題。

3、什麼是內存模型

緩存一致性問題、處理器器優化的指令重排問題是硬件的不斷升級致使的。那麼,有沒有什麼機制能夠很好的解決上面的這些問題呢?

最簡單直接的作法就是廢除處理器和處理器的優化技術、廢除CPU緩存,讓CPU直接和主存交互。可是,這麼作雖然能夠保證多線程下的併發問題。可是,這就有點因噎廢食了。

因此,爲了保證併發編程中能夠知足原子性、可見性及有序性。有一個重要的概念,那就是——內存模型。

定義

爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。

目的

解決CPU多級緩存、處理器優化、指令重排等致使的內存訪問問題,保證併發場景下的一致性、原子性和有序性。

4、Java內存模型

前面介紹過了計算機內存模型,這是解決多線程場景下併發問題的一個重要規範。那麼具體的實現是如何的呢,不一樣的編程語言,在實現上可能有所不一樣。

定義

Java程序是須要運行在Java虛擬機上面的,Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java程序在各類平臺下對內存的訪問都能保證效果一致的機制及規範。

Java內存模型規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量的傳遞均須要本身的工做內存和主存之間進行數據同步進行。而JMM就做用於工做內存和主存之間數據同步過程。他規定了如何作數據同步以及何時作數據同步。

主內存和工做內存

能夠簡單的類比成計算機內存模型中的主存和緩存的概念。特別須要注意的是,主內存和工做內存與JVM內存結構中的Java堆、棧、方法區等並非同一個層次的內存劃分,沒法直接類比。《深刻理解Java虛擬機》中認爲,若是必定要勉強對應起來的話,從變量、主內存、工做內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分。工做內存則對應於虛擬機棧中的部分區域。

JMM

是一種規範,目的是解決因爲多線程經過共享內存進行通訊時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。

Java內存模型的實現

在Java中提供了一系列和併發處理相關的關鍵字,好比volatile、synchronized、final、concurren包等。其實這些就是Java內存模型封裝了底層的實現後提供給程序員使用的一些關鍵字。

原子性

在Java中,爲了保證原子性,提供了兩個高級的字節碼指令monitorenter和monitorexit。這兩個字節碼,在Java中對應的關鍵字就是synchronized。所以,在Java中可使用synchronized來保證方法和代碼塊內的操做是原子性的。

可見性

Java內存模型是經過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存做爲傳遞媒介的方式來實現的。Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後能夠當即同步到主內存,被其修飾的變量在每次是用以前都從主內存刷新。所以,可使用volatile來保證多線程操做時變量的可見性。除了volatile,Java中的synchronized和final兩個關鍵字也能夠實現可見性。只不過實現方式不一樣,這裏再也不展開了。

有序性

在Java中,可使用synchronized和volatile來保證多線程之間操做的有序性。實現方式有所區別:volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只容許一條線程操做。

經過以上能夠發現,好像synchronized關鍵字是萬能的,他能夠同時知足以上三種特性,這其實也是不少人濫用synchronized的緣由。可是synchronized是比較影響性能的,雖然編譯器提供了不少鎖優化技術,可是也不建議過分使用。

相關文章
相關標籤/搜索