成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java內(nèi)存模型

fantix / 1216人閱讀

摘要:內(nèi)存模型指定了如何與計(jì)算機(jī)內(nèi)存協(xié)同工作。內(nèi)部的內(nèi)存模型內(nèi)存模型在內(nèi)部使用,將內(nèi)存分為了線程棧和堆。下面的圖從邏輯角度給出了內(nèi)存模型每個(gè)運(yùn)行在內(nèi)部的線程都有自己的線程棧。部分線程棧和堆可能在某些時(shí)候會(huì)占用緩存和內(nèi)部寄存器。

Java內(nèi)存模型指定了JVM如何與計(jì)算機(jī)內(nèi)存協(xié)同工作。JVM是整個(gè)計(jì)算機(jī)的模型因此這個(gè)模型包含了內(nèi)存模型,也就是Java內(nèi)存模型。

如果你像要設(shè)計(jì)正確行為的并發(fā)程序,那么了解Java內(nèi)存模型是非常重要的。Java內(nèi)存模型指定了如何以及何時(shí)不同的線程能夠看到其他線程寫入共享變量的值,以及如何在需要的時(shí)候如何同步訪問共享變量。

最初的Java內(nèi)存模型是不足的,因此Java內(nèi)存模型在Java1.5做了改進(jìn),這個(gè)版本的Java內(nèi)存模型在Java8中仍然被使用。

內(nèi)部的Java內(nèi)存模型

Java內(nèi)存模型在JVM內(nèi)部使用,將內(nèi)存分為了線程棧和堆。下面的圖從邏輯角度給出了Java內(nèi)存模型:

每個(gè)運(yùn)行在JVM內(nèi)部的線程都有自己的線程棧。線程棧包含關(guān)于線程調(diào)用的哪個(gè)方法到達(dá)了當(dāng)前執(zhí)行點(diǎn)的信息。我對(duì)此引用為“調(diào)用?!?。隨著線程執(zhí)行代碼,調(diào)用棧會(huì)發(fā)生變化。

調(diào)用棧還包含每個(gè)被執(zhí)行的方法的所有本地變量(所有調(diào)用棧上的方法)。一個(gè)線程只能夠訪問它自己的線程棧。由一個(gè)線程創(chuàng)建的本地變量對(duì)其他線程不可見。即使兩個(gè)線程執(zhí)行同一段代碼,這兩個(gè)線程也會(huì)在他們各自的線程棧中創(chuàng)建這段代碼涉及的本地變量。因此,每個(gè)線程都有自己版本的本地變量。

所有內(nèi)建類型的本地變量(boolean,byte,short,char,int,long,float,double)被存儲(chǔ)在線程棧并且對(duì)其他線程不可見。一個(gè)線程可能會(huì)傳遞一個(gè)內(nèi)建類型變量的副本給其他線程,但是它不會(huì)貢獻(xiàn)它自己的內(nèi)建本地變量。

堆包含了你的Java程序中創(chuàng)建的所有對(duì)象,不管是哪個(gè)線程創(chuàng)建的。這包含了對(duì)象版本的內(nèi)建類型(如Byte,Integer,Long等等)。如果一個(gè)對(duì)象唄創(chuàng)建并被復(fù)制給一個(gè)本地變量,或者被創(chuàng)建為一個(gè)成員變量都是沒關(guān)系的,對(duì)象仍然存儲(chǔ)在堆上。

下圖給出了調(diào)用棧和存儲(chǔ)在線程棧中的本地變量,以及存儲(chǔ)在堆上的對(duì)象:

一個(gè)本地變量可能是一個(gè)內(nèi)建類型,這種情況它完全存儲(chǔ)在線程棧。

一個(gè)本地變量可能是一個(gè)對(duì)象的引用。這種情況這個(gè)引用(本地變量)存儲(chǔ)在線程棧中,但是對(duì)象本身存儲(chǔ)在堆上。

一個(gè)對(duì)象可能包含方法,并且這些方法可能包含本地變量。這些本地變量存儲(chǔ)在線程棧,即使方法所屬對(duì)象存儲(chǔ)在堆上。

一個(gè)對(duì)象的成員變量和對(duì)象一起存儲(chǔ)在堆上。對(duì)于成員變量是內(nèi)建類型,或者它是對(duì)象的引用都是如此。

靜態(tài)類變量和類定義一起存儲(chǔ)在堆上。

堆上的對(duì)象能夠被所有擁有這個(gè)對(duì)象引用的線程訪問。當(dāng)一個(gè)線程訪問一個(gè)對(duì)象,它也可以訪問這個(gè)對(duì)象的成員變量。如果兩個(gè)線程在同一個(gè)對(duì)象上同時(shí)調(diào)用它的同一個(gè)方法,這兩個(gè)線程會(huì)同時(shí)又權(quán)限訪問這個(gè)對(duì)象的成員變量,但是每個(gè)線程會(huì)有它自己的本地變量副本。

下面的圖給出了上面所說的:

兩個(gè)線程有同一組本地變量。一個(gè)本地變量(Local Variable 2)指向了堆上的一個(gè)共享對(duì)象(Object3)。每個(gè)線程都有對(duì)同一個(gè)對(duì)象的不同引用。它們的引用是本地變量并且存儲(chǔ)在各自的線程棧上,盡管這兩個(gè)不同的引用指向堆上的同一個(gè)對(duì)象。

注意共享對(duì)象(Object 3)有一個(gè)對(duì)Object2和Object4的引用作為它的成員變量,通過Object3中的這些成員變量引用,這兩個(gè)線程可以訪問Object2和Object4。

圖中還給出了一個(gè)本地變量指向堆上的兩個(gè)不同的對(duì)象。這個(gè)例子中引用指向了兩個(gè)不同對(duì)象(Object1和Object5),而不是同一個(gè)對(duì)象。理論上所有線程如果有指向所有有對(duì)象的引用,那么這些線程可以訪問到Object1和Object5。但是在圖中每個(gè)線程只有一個(gè)引用指向這兩個(gè)對(duì)象之一。

那么,什么樣的Java代碼能夠滿足上面的內(nèi)存圖示?請(qǐng)看下面的簡單代碼:

public class MyRunnable implements Runnable {
  
  public void run() {
    methodOne();
  }

  public void methodOne() {
    int localVariable1 = 45;
    
    MyShareObject localVariable2 = MyShareObject.shareInstance;
    
    // ... do more with local variables.
    
    methodTwo();
  }

  public void methodTwo() {
    Integer localVariable1 = new Integer(99);
    
    // ... do more with local variable.
  }
}
public class MyShareObject {
  
  // static variable pointing to instance of MyShareObject

  public static final MySharedObject sharedInstance = new MySharedObject();

  // member variable pointing to two objects on the heap

  public Integer object2 = new Integer(22);
  public Integer object4 = new Integer(44);

  public long member1 = 12345;
  public long member2 = 67890;
}

如果兩個(gè)線程執(zhí)行run()方法,則圖中所示就是結(jié)果。run()方法調(diào)用methodOne()然后methodOne()調(diào)用methodTwo()。

methodOne()聲明了一個(gè)內(nèi)建類型的本地變量(int類型的localVariable1),另一個(gè)本地變量是一個(gè)對(duì)象的引用(localVariable2)。

每個(gè)執(zhí)行methodOne()的線程會(huì)在各自的線程棧上創(chuàng)建它自己的localVariable1和localVariable2的副本。兩個(gè)localVariable1變量完全和對(duì)方?jīng)]有關(guān)系,只是活在各自的線程棧上。一個(gè)線程不能看到另一個(gè)線程它自己的localVariable1副本變化。

每個(gè)執(zhí)行methodOne()的線程也會(huì)在各自的線程棧上創(chuàng)建它們自己的localVariable2副本。然而這兩個(gè)不同的localVariable2副本是指向堆上的同一個(gè)對(duì)象。代碼設(shè)置localVariable2指向被一個(gè)靜態(tài)變量引用的對(duì)象。這里只有一個(gè)靜態(tài)變量的副本并且這個(gè)副本存儲(chǔ)在堆上。因此所有l(wèi)ocalVariable2的這兩個(gè)副本都指向同一個(gè)被靜態(tài)變量指向的MySharedObject實(shí)例。MySharedObject實(shí)例存儲(chǔ)在堆上,它對(duì)應(yīng)圖上的Object3。

注意MySharedObject類還包含了兩個(gè)成員變量。成員變量和這個(gè)對(duì)象一起存儲(chǔ)在堆上。這兩個(gè)成員變量指向了兩個(gè)Integer對(duì)象。這些Integer對(duì)象對(duì)應(yīng)圖上Object2和Object4。

注意methodTwo()創(chuàng)建了一個(gè)名為localVariable1的本地變量,這個(gè)本地變量是一個(gè)Integer對(duì)象的引用。這個(gè)方法設(shè)置localVariable1引用指向了一個(gè)新的Integer實(shí)例。localVariable1引用會(huì)存儲(chǔ)在執(zhí)行methodTwo()方法的每個(gè)線程的副本中。兩個(gè)被實(shí)例化的Integer對(duì)象會(huì)存儲(chǔ)在堆中,但是由于每次方法執(zhí)行時(shí)都創(chuàng)建了一個(gè)新的Integer對(duì)象,兩個(gè)線程會(huì)執(zhí)行并創(chuàng)建兩個(gè)不同的Integer實(shí)例。methodTwo()中創(chuàng)建的Integer對(duì)象對(duì)應(yīng)圖中的Object1和Object5。

注意MySharedObject中的兩個(gè)long型的成員變量是內(nèi)建類型。由于這些變量的成員變量,因此它們?nèi)匀缓蛯?duì)象一起存儲(chǔ)在堆上。只有本地變量會(huì)存儲(chǔ)在線程棧上。

硬件內(nèi)存架構(gòu)

現(xiàn)代硬件內(nèi)存架構(gòu)和內(nèi)部Java內(nèi)存模型有些區(qū)別。對(duì)于了解Java內(nèi)存模型如何工作,了解硬件內(nèi)存架構(gòu)也很重要。這部分描述通用硬件內(nèi)存架構(gòu),下一個(gè)部分會(huì)描述Java內(nèi)存模型是如何工作在硬件內(nèi)存之上。

這里有一個(gè)簡單的計(jì)算機(jī)硬件架構(gòu)模型:

現(xiàn)代計(jì)算機(jī)通常有2個(gè)或更多的CPU。有些CPU還有多個(gè)核。重點(diǎn)是,在一個(gè)有2個(gè)或更多CPU的計(jì)算機(jī)上,有多個(gè)線程同時(shí)運(yùn)行是可能的。每個(gè)CPU能夠在任何時(shí)候運(yùn)行一個(gè)線程。這意味著如果你的Java程序是多線程的,每個(gè)CPU一個(gè)線程同時(shí)并發(fā)運(yùn)行在你的Java程序中。

每個(gè)CPU包含一組寄存器,本質(zhì)行是CPU內(nèi)的存儲(chǔ)。CPU在這些寄存器中執(zhí)行操作會(huì)比在主存中快的多。這是因?yàn)镃PU能夠更快的訪問這些寄存器。

每個(gè)CPU可能還有一個(gè)CPU緩存層。實(shí)際上,大部分現(xiàn)代CPU都有一個(gè)特定大小的緩存層。CPU能比訪問主存更快的訪問緩存,但是一般不會(huì)比訪問它的內(nèi)部寄存器更快。因此,CPU緩存是一個(gè)介于內(nèi)部寄存器和主存之間的地方。有些CPU可能有多級(jí)緩存(Level1和Level2),但是這對(duì)理解Java內(nèi)存模型如何與內(nèi)存交互來說并不是很需要知道。

一個(gè)計(jì)算機(jī)也包含一個(gè)主存區(qū)域(RAM)。所有CPU都能訪問主存。主存區(qū)域比CPU緩存大的多。

一般來說,當(dāng)一個(gè)CPU需要訪問主存,它會(huì)將主存的一本讀取到它的CPU緩存。甚至它可能會(huì)讀取部分緩存到它的內(nèi)部寄存器并在其上操作。當(dāng)CPU需要將結(jié)果寫回到主存它會(huì)將值從內(nèi)部寄存器刷到緩存,在摸個(gè)時(shí)間點(diǎn)將緩存中的值刷回到主存。

當(dāng)CPU需要在緩存中存儲(chǔ)一些其他東西時(shí),緩存中存儲(chǔ)的值會(huì)被刷回到主存。每次緩存更新時(shí),CPU不必讀寫整塊緩存。對(duì)于緩存在較小內(nèi)存塊上的更新的標(biāo)準(zhǔn)說法是“cache lines”。一個(gè)或多個(gè)cache lines會(huì)被讀到緩存,一個(gè)或多個(gè)cache lines會(huì)被刷回主存。

連接Java內(nèi)存模型和硬件內(nèi)存架構(gòu)

上面說道,Java內(nèi)存模型和硬件內(nèi)存架構(gòu)不同。硬件內(nèi)存架構(gòu)不會(huì)分辨線程棧和堆。在硬件上,線程棧和堆都定位到主存。部分線程棧和堆可能在某些時(shí)候會(huì)占用CPU緩存和內(nèi)部CPU寄存器。如下圖所示:

當(dāng)對(duì)象和變量能被存儲(chǔ)在計(jì)算機(jī)的不同內(nèi)存區(qū)域時(shí),特定的問題就會(huì)發(fā)生。兩個(gè)主要問題是:

線程更新(寫)到共享變量的可見性

讀寫檢查共享變量時(shí)發(fā)生的競態(tài)條件

這些問題會(huì)在下面的部分解釋。

共享變量的可見性

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,如果沒有恰當(dāng)使用volatile聲明或者同步,一個(gè)線程對(duì)共享變量的更新對(duì)其他線程可能會(huì)不可見。

想象一個(gè)共享對(duì)象初始存儲(chǔ)在主存。一個(gè)運(yùn)行在CPU1上的線程將這個(gè)共享變量讀取到它的CPU緩存,然后對(duì)這個(gè)共享變量做一些改變,只要CPU緩存沒有被刷回主存,這個(gè)共享變量的變更版本對(duì)運(yùn)行在其他CPU上的線程就是不可見的。這種方式每個(gè)線程會(huì)有這個(gè)共享變量的本地副本,每個(gè)副本位于不同的CPU緩存中。

下圖展示了這種情況。運(yùn)行在左邊CPU的線程將共享變量拷貝到它的CPU緩存,并將這個(gè)對(duì)象的count變量變?yōu)?.這個(gè)變化對(duì)運(yùn)行在右邊CPU上的線程不可見,因?yàn)閷?duì)count的更新還沒有刷回主存。

為了解決這個(gè)問題,你可以使用Kava的volatile關(guān)鍵字。volatile關(guān)鍵字能夠保證一個(gè)給定的變量從主存中讀取,并且當(dāng)變量更新時(shí)會(huì)寫回主存。

競態(tài)條件

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,多余一個(gè)線程更新這個(gè)共享對(duì)象的變量,靜態(tài)條件就可能發(fā)生。

想象如果線程A讀取了一個(gè)共享對(duì)象的count變量到它的CPU緩存,線程B做同樣的事情,但是是在一個(gè)不同的CPU緩存?,F(xiàn)在線程A對(duì)count加1,線程B也對(duì)count加1.現(xiàn)在count被加了兩次,每次都是在不同的CPU緩存。

如果這些增加的操作被順序執(zhí)行,那么變量count會(huì)增加兩次并有初始值+2的值被寫回主存。

但是這兩次增加是在沒有同步的情況下并發(fā)操作的。不管線程A還是線程B將它們對(duì)count的更新版本寫回主存,count只會(huì)得到初始值+1,盡管有兩次更新。

下面的圖描述了靜態(tài)條件:

為了解決這個(gè)問題你可以用一個(gè)synchronized塊。一個(gè)synchronized塊保證了同時(shí)只有一個(gè)線程能進(jìn)入一個(gè)給定的關(guān)鍵代碼區(qū)域。synchronized塊也保證了所有在synchronized塊中訪問的變量會(huì)從主存中讀取,當(dāng)一個(gè)線程退出synchronized塊,所有對(duì)變量的更新會(huì)再次刷回主存,不管這個(gè)變量是否被聲明為volatile。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/75958.html

相關(guān)文章

  • 來,了解一下Java內(nèi)存模型(JMM)

    摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會(huì)存在緩存一致性問題。有沒有發(fā)現(xiàn),緩存一致性問題其實(shí)就是可見性問題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    kviccn 評(píng)論0 收藏0
  • 來,了解一下Java內(nèi)存模型(JMM)

    摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會(huì)存在緩存一致性問題。有沒有發(fā)現(xiàn),緩存一致性問題其實(shí)就是可見性問題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    eccozhou 評(píng)論0 收藏0
  • 深入理解Java內(nèi)存模型(七)——總結(jié)

    摘要:編譯器,和處理器會(huì)共同確保單線程程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果相同。正確同步的多線程程序的執(zhí)行將具有順序一致性程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。 前情提要 深入理解Java內(nèi)存模型(六)——final 處理器內(nèi)存模型 順序一致性內(nèi)存模型是一個(gè)理論參考模型,JMM和處理器內(nèi)存模型在設(shè)計(jì)時(shí)通常會(huì)把順序一致性內(nèi)存模型作為參照。JMM和處理器內(nèi)...

    paney129 評(píng)論0 收藏0
  • 簡述Java內(nèi)存模型

    摘要:內(nèi)存模型即,簡稱,其規(guī)范了虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過的值,以及在必須時(shí),如何同步訪問共享變量。內(nèi)存模型要求調(diào)用棧和本地變量存放在線程棧上,對(duì)象存放在堆上。 Java內(nèi)存模型即Java Memory Model,簡稱JMM,其規(guī)范了Java虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過的值,以及在必須時(shí),...

    ACb0y 評(píng)論0 收藏0
  • JVM 探究(一):JVM內(nèi)存模型概念模型

    摘要:作為一個(gè)程序員,不了解內(nèi)存模型就不能寫出能夠充分利用內(nèi)存的代碼。程序計(jì)數(shù)器是在電腦處理器中的一個(gè)寄存器,用來指示電腦下一步要運(yùn)行的指令序列。在虛擬機(jī)中,本地方法棧和虛擬機(jī)棧是共用同一塊內(nèi)存的,不做具體區(qū)分。 作為一個(gè) Java 程序員,不了解 Java 內(nèi)存模型就不能寫出能夠充分利用內(nèi)存的代碼。本文通過對(duì) Java 內(nèi)存模型的介紹,讓讀者能夠了解 Java 的內(nèi)存的分配情況,適合 Ja...

    cnTomato 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

fantix

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<