java記憶體模型(Java Memory Model)

來源:互聯網
上載者:User

標籤:jvm   記憶體   多線程   競爭   緩衝   


內容導航:

l  Java記憶體模型

l  硬體儲存體繫結構

l  Java記憶體模型和硬體儲存體系之間的橋樑:

u  共用對象的可見度

u  競爭條件

 

Java記憶體模型規定了JVM如何與電腦儲存系統(RAM)協調工作。JVM是一個虛擬機器模型,因此這個模型自然包括一個記憶體的模型

 

理解java記憶體模型對於設計正確的並發程式很重要。JVM規定了不同線程何時以及如何能看到那些被共用變數的讀寫,如何同步對共用變數的存取控制。

 

最初的java記憶體模型並不完善,所以他在java1.5中被修改了。下面的記憶體模型在java1.8中仍然使用。

 

Java記憶體模型

 

JVM中記憶體模型被劃分為棧(stack)和堆(heap),是記憶體的邏輯圖


在JVM中啟動並執行每一個線程有自己的棧空間。每個線程棧包含有線程調用方法當前執行位置的指標。我們把它叫做棧指標。當線程執行他的代碼時候,棧指標會改變。共用一塊堆空間。

線程棧也包含所有正在被執行的方法的局部變數。一個線程只能訪問他自己的棧。每一個線程建立的局部變數對其他線程是不可見的。縱使兩個線程正在執行同一段代碼,他們也只會建立屬於自己棧空間的局部變數。也就是說每個線程有他自己的局部變數,相互之間不影響。

 

所有的局部基礎資料型別 (Elementary Data Type)(boolean、byte、short、char、int、long、float、double)完全儲存線上程棧空間裡,並且其他線程不可見。一個線程可以傳遞自己局部基本資料的拷貝給另一個線程,但是他們之間並不共用。

 

堆空間中存放的是應用程式中所有的對象,不管是哪一個線程建立的。並且包括基本資料對象(eg.Byte,Integer,Long等)。不管這個對象是作為局部變數被建立,還是作為另一個對象的成員變數被建立,他都在堆中。即一切對象都在堆空間。

 

看:


一個局部變數可能是基礎資料型別 (Elementary Data Type),還可能是一個對象的引用,他們都在建立自己的線程棧空間,引用所指向的真正的對象在堆空間。

一個對象的成員方法,並且這些方法中也可能包含局部變數,這些局部變數在所屬的線程棧中

一個對象的成員變數,不管是基本類型還是對象的引用,都存在這個堆空間的對象的內部

 

同一個對象可以被不同的擁有這個對象的引用的線程訪問,並且可以訪問對象的成員變數。

如果兩個線程同一時刻調用相同對象的成員方法,他們都能訪問對象的成員變數,但是各自有自己的局部變數的拷貝。

下面的圖闡述了上述內容:


硬體儲存體繫結構:

下面是簡單的現代電腦的儲存結構:


現代電腦通常有2個甚至更多的CPU,這些CPU中的一些還可能是多核的。關鍵點就是多CPU的電腦允許多個線程同時運行(是真正的並行而非並發),每一個cpu在給定的時間片運行一個線程。如果java程式是多線程的,每個線程就可以在各個cpu上同時運行。

 

每個cpu都包含有一系列寄存器。cpu直接從寄存器執行運算比從記憶體要快很多倍。

每一個cpu還可能有cpu緩衝(我們熟稱的cache)。實際上大多數現代cpu都有不同容量的cache。Cpu從cache存取又比從記憶體快,但是比寄存器又慢一些。一些cpu還有多級緩衝(L1、L2等),但是這並不影響理解java記憶體模型,我們所要知道的就是在記憶體和cpu之間還有一層緩衝cache。

 

通常,cpu訪問主存的時候需要讀一部分到cache,還可能把cache的一部分有讀到寄存器,然後執行運算。當cpu需要把運算結果寫回到主存的時候,會先把值從寄存器更新到cache,cache放滿後再寫回主存。

 

儲存在cache中的值通常在cpu需要儲存其他的東西時一起被寫回到記憶體。Cache把裡面儲存的內容一次性寫回到主存。這個過程並不一定讀寫整個cache,通常情況下是更新cache中更小的單位,叫做cache lines 緩衝行。一行或多行cache line會從主存讀取到cache,或者從cache被寫回主存。

 

Java記憶體模型和硬體儲存體系之間的橋樑

正如已經提及的,java記憶體模型和硬體儲存體系是不同的。硬體儲存體系並不會區分堆和棧。

在硬體層面上,堆和棧都分配在主存中。堆和棧中的一部分還可能在cache和寄存器中,如:

 

當對象和變數被儲存在電腦不同的儲存地區(rejister、cache、main-memory)的時候,就會出現問題了。有兩個問題如下:

l  線程對共用變數讀寫的可見度

l  讀寫檢查共用變數的競爭條件

 

共用對象的可見度

如果兩個或者多個線程共用一個對象而沒有用volatile聲明或者synchronize同步,某一個線程對共用對象的更新對其他線程可能是不可見的。

設想一下一個開始被存在主記憶體的共用對象。運行在cpu1上的線程把這個共用對象讀入到cache。然後對這個共用對象做更改。只要cache還沒有被重新整理到主存,對象的更改版本對運行在其他cpu的線程就是不可見的。這樣一來,每個線程就使用的是自己對共用對象的拷貝,這些拷貝儲存在各自cpu cache中。

 

下面這幅圖闡述了這種情況。一個運行在左側cpu的線程把共用對象拷貝到自己的cpu cache中,並且把count值改為2.這個改變對運行在右側cpu的線程是不可見的。因為對count的更新並沒有重新整理到主存。


要解決這個問題,我們可以使用java的volatile關鍵字。Volatile關鍵字能夠確保被聲明的變數直接從主存讀取或者是直接在主存更新而不經過cache中介層。

 

競爭條件

多個線程對共用變數的更新還會引發競爭。

設想線程A讀取共用變數的count欄位到他的cache,線程B也是。現線上程A對count加1,線程B也是。現在var1已經被增加兩次,每個cpu一次。

 

如果這些增加是被順序執行(先後次序:即A read increament writeback  Bread increament writeback)的,那麼變數count將會順序加1兩次,最終結果是原始值加2寫回主存。

但是,如果兩次增加被並發執行(交叉次序)而沒有適當的同步,那麼不管是A還是B寫回到主存的結果都是原始值加1,儘管做了兩次加。

下面是上述問題的圖:


為瞭解決這個問題,引入java的synchronized block,讓某一系列操作成為原子性的,即不可以被打斷(類似資料庫中的事務transaction),從而實現不同線程對某一代碼塊的互斥訪問。

 翻譯原文地址:http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

 

java記憶體模型(Java Memory Model)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.