Google—Java Memory Model

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存儲器模型
  • 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)用棧和本地變量以及存儲在堆上的對象:


Java內(nèi)存模型顯示了本地變量和對象存儲在內(nèi)存中的位置。

以下是上圖所示的圖:

Java內(nèi)存模型顯示從局部變量到對象以及從對象到其他對象的引用。

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)代硬件內(nèi)存架構(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寄存器中。
    這在圖中說明:
CPU內(nèi)部寄存器,CPU緩存和主內(nèi)存之間的線程堆棧和堆的劃分。
  • 當(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)存。

Java內(nèi)存模型中的可見性問題。
要解決這個問題,可以使用Java的volatile關(guān)鍵字。該volatile 關(guān)鍵字可以確保一個給定的變量從主內(nèi)存中直接讀取和更新的時候總是寫回主內(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ā)生:

Java內(nèi)存模型中的競爭條件問題。
要解決此問題,您可以使用Java同步塊。

  • 同步塊保證在任何給定時間只有一個線程可以進(jìn)入代碼的給定關(guān)鍵部分。
  • 同步塊還保證在同步塊內(nèi)訪問的所有變量將從主存儲器讀入,并且當(dāng)線程退出同步塊時,所有更新的變量將被再次刷新回主存儲器,而不管該變量是否被聲明為volatile或不。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,818評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,740評論 18 399
  • 好久都沒有成就感了…
    果果凍凍凍閱讀 214評論 0 0
  • 每個人對自己要求的生活都太相同,有人喜歡被注意,有人喜歡被忽視。 性格開朗的人喜歡一個相對活潑的環(huán)境,...
    旸蹊閱讀 157評論 0 0
  • 作業(yè)一:每日晨間朋友圈見證(必須在12點(diǎn)之前完成) 作業(yè)二:每天朋友圈心得(當(dāng)天晚上12點(diǎn)之前完成) 作業(yè)三:每天...
    米娜_9228閱讀 219評論 0 0

友情鏈接更多精彩內(nèi)容