http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
概述
The Java memory model specifies how the Java virtual machine works with the computer's memory (RAM).
- 原始的Java內(nèi)存模型不足,所以Java內(nèi)存模型在Java 1.5中進(jìn)行了修改。這個版本的Java內(nèi)存模型仍然在Java 8中使用。
The Internal Java Memory Model
- The Java memory model used internally in the JVM divides memory between thread stacks and the heap. (JVM內(nèi)部使用的Java內(nèi)存模型將線程堆棧和堆之間的內(nèi)存分割。)
該圖從邏輯角度說明了Java內(nèi)存模型:

Java虛擬機(jī)中運(yùn)行的每個線程都有自己的線程堆棧。線程堆棧包含有關(guān)線程調(diào)用哪些方法來達(dá)到當(dāng)前執(zhí)行點(diǎn)的信息。我將其稱為“調(diào)用堆?!薄?br> 當(dāng)線程執(zhí)行其代碼時,調(diào)用堆棧發(fā)生更改。
線程堆棧還包含執(zhí)行的每個方法的所有局部變量(調(diào)用堆棧中的所有方法)。
一個線程只能訪問它自己的線程堆棧。線程所創(chuàng)建的局部變量對于所有其他線程,不同于創(chuàng)建線程的線程。即使兩個線程執(zhí)行完全相同的代碼,兩個線程仍然會在每個自己的線程堆棧中創(chuàng)建該代碼的局部變量。因此,每個線程都有自己的每個局部變量的版本。
基本類型(所有局部變量 boolean,byte,short,char,int、long, float,double)完全存儲在線程棧上,因此不是其他線程可見。
一個線程可以將一個pritimive變量的副本傳遞給另一個線程,但是它不能共享原始局部變量本身。
堆包含您的Java應(yīng)用程序中創(chuàng)建的所有對象,無論創(chuàng)建對象的線程如何。
這包括原語類型(例如對象的版本Byte,Integer,Long等等)。如果對象被創(chuàng)建并分配給局部變量,或者創(chuàng)建為另一個對象的成員變量,則對象仍然存儲在堆上,這并不重要。
這是一個圖示出了存儲在線程堆棧上的調(diào)用棧和本地變量以及存儲在堆上的對象:

以下是上圖所示的圖:

Hardware Memory Architecture
現(xiàn)代硬件內(nèi)存架構(gòu)與內(nèi)部Java內(nèi)存模型有所不同。了解硬件內(nèi)存架構(gòu)也很重要,以了解Java內(nèi)存模型的工作原理。本節(jié)介紹常見的硬件內(nèi)存架構(gòu),后面的部分將介紹Java內(nèi)存模型的工作原理。
以下是現(xiàn)代計(jì)算機(jī)硬件架構(gòu)的簡化圖:

現(xiàn)代計(jì)算機(jī)通常有2個或更多的CPU。其中一些CPU也可能有多個內(nèi)核。
關(guān)鍵是,在具有2個或更多個CPU的現(xiàn)代計(jì)算機(jī)上,可以同時運(yùn)行多個線程。每個CPU都可以在任何給定的時間運(yùn)行一個線程。這意味著如果您的Java應(yīng)用程序是多線程的,則每個CPU可能會在Java應(yīng)用程序中同時(并發(fā))運(yùn)行一個線程。每個CPU都包含一組本質(zhì)上是CPU內(nèi)存的寄存器。CPU可以在這些寄存器上執(zhí)行的操作比對主存儲器中的變量執(zhí)行的操作要快得多。這是因?yàn)镃PU可以訪問這些寄存器比訪問主內(nèi)存的速度快得多。
每個CPU也可以具有CPU緩存存儲器層。事實(shí)上,大多數(shù)現(xiàn)代CPU都有一些大小的緩存內(nèi)存層。CPU可以比主存儲器訪問其高速緩存的速度快,但通常不如訪問其內(nèi)部寄存器一樣快。因此,CPU緩存內(nèi)存位于內(nèi)部寄存器和主存儲器的速度之間。某些CPU可能有多個緩存層(1級和2級),但是要了解Java內(nèi)存模型如何與內(nèi)存進(jìn)行交互,這并不重要。
重要的是知道CPU可以有某種緩存內(nèi)存層。計(jì)算機(jī)還包含主存儲區(qū)(RAM)。所有CPU都可以訪問主存儲器。主存儲區(qū)通常遠(yuǎn)大于CPU的高速緩沖存儲器。
通常,當(dāng)CPU需要訪問主存儲器時,它會將主存儲器的一部分讀入其CPU緩存。甚至可以將部分緩存讀入其內(nèi)部寄存器,然后對其執(zhí)行操作。
當(dāng)CPU需要將結(jié)果寫回主內(nèi)存時,它將從內(nèi)部寄存器中將值刷新到高速緩存內(nèi)存,并在某些時候?qū)⒅邓⑿禄刂鲀?nèi)存。當(dāng)CPU需要在高速緩沖存儲器中存儲其他內(nèi)容時,存儲在高速緩沖存儲器中的值通常會刷新回主存儲器。CPU緩存一次可以將數(shù)據(jù)寫入其內(nèi)存的一部分,并一次刷新其內(nèi)存的一部分。
每次更新時,它不必讀取/寫入完整的緩存。
通常,緩存在被稱為“高速緩存行”的更小的存儲塊中被更新。
可以將一個或多個高速緩存行讀入高速緩沖存儲器,并且可以將一個或多個高速緩存線重新刷回主存儲器。
Bridging The Gap Between The Java Memory Model And The Hardware Memory Architecture
- 如前所述,Java內(nèi)存模型和硬件內(nèi)存架構(gòu)是不同的。硬件內(nèi)存架構(gòu)不區(qū)分線程堆棧和堆。在硬件上,線程堆棧和堆都位于主內(nèi)存中。線程堆棧和堆的一部分有時可能存在于CPU高速緩存和內(nèi)部CPU寄存器中。
這在圖中說明:

- 當(dāng)對象和變量可以存儲在計(jì)算機(jī)的各種不同的存儲區(qū)域中時,可能會出現(xiàn)某些問題。兩個主要問題是:
線程更新(寫入)到共享變量的可見性。
閱讀,檢查和寫入共享變量時的競爭條件。
這兩個問題將在以下部分中解釋。
Visibility of Shared Objects
如果兩個或多個線程共享一個對象,沒有正確使用volatile聲明或同步,一個線程所做的共享對象的更新對其他線程可能不可見。
假設(shè)共享對象最初存儲在主內(nèi)存中。在CPU上運(yùn)行的線程然后將共享對象讀入其CPU緩存。在那里它對共享對象進(jìn)行了更改。
只要CPU緩存沒有被刷新到主內(nèi)存,共享對象的更改版本對于在其他CPU上運(yùn)行的線程是不可見的。
這樣一來,每個線程可能最終都有自己的共享對象副本,每個副本都坐在不同的CPU緩存中。
下圖說明了草圖的情況。
在左CPU上運(yùn)行的一個線程將共享對象復(fù)制到其CPU緩存中,并將其
count變量更改為2.對于在右側(cè)CPU上運(yùn)行的其他線程,此更改不可見,因?yàn)楦耤ount尚未刷新到主內(nèi)存。

Race Conditions
如果兩個或多個線程共享對象,并且多個線程更新該共享對象中的變量,則 可能會發(fā)生競爭條件。
假設(shè)線程A將count共享對象的變量讀入其CPU緩存中。想象一下,線程B執(zhí)行相同的操作,但是進(jìn)入不同的CPU緩存。
現(xiàn)在線程A添加一個count,線程B也一樣?,F(xiàn)在var1已經(jīng)增加了兩次,每次CPU緩存一次。如果這些增量依次執(zhí)行,則變量count
將被遞增兩次,并將原始值+ 2寫回主存儲器。但是,兩個增量是在沒有正確同步的情況下同時進(jìn)行的。不管線程A和B中哪一個將其更新版本寫
count回到主內(nèi)存,盡管有兩個增量,但更新后的值將僅比原始值高1。
該圖示出了如上所述的競爭條件的問題的發(fā)生:

- 同步塊保證在任何給定時間只有一個線程可以進(jìn)入代碼的給定關(guān)鍵部分。
- 同步塊還保證在同步塊內(nèi)訪問的所有變量將從主存儲器讀入,并且當(dāng)線程退出同步塊時,所有更新的變量將被再次刷新回主存儲器,而不管該變量是否被聲明為volatile或不。