Java內存模型主要是圍繞着線程併發過程當中如何處理原子性、可見性和順序性這三個特徵來設計的。linux
一、原子性c++
原子性表示任意時刻只有一個線程能夠執行某一段功能代碼,以防止多個線程同時訪問某些共享數據時,形成錯誤。windows
二、可見性數組
可見性是指一個線程修改了某個共享變量後,其餘線程可以馬上訪問到被修改後的最新數據,也就是共享數據對其餘線程都是可見的。
volatile修飾的變量在修改後會當即同步到主內存,在使用時會從新從主內存中讀取。volatile變量是依賴主內存爲中介來保證多線程下變量對其餘線程的可見性。
synchronized關鍵字是經過在unlock以前必須把變量同步回主內存來實現可見性的。
final關鍵字則是由於變量在初始化後,值就不會更改,因此只要在初始化過程當中沒有把this指針傳遞出去也能保證對其餘線程的可見性。安全
三、有序性多線程
程序在運行時,指令的執行順序並非嚴格按照從上到下順序執行的,可能會進行指令重排。根據CPU流水線做業,通常來講,簡單的操做會先執行,複雜的操做後執行。
有序性從不一樣的角度來看是不一樣的。單純從單線程來看都是有序的,但到了多線程就不同了。能夠這麼理解,若是在一個線程內部觀察,全部操做都是有序的。可是若是在一個線程內觀察另外一個線程,操做多是無序的。也就是CPU進行的指令重排序對單線程程序而言,不會有什麼問題,可是對於多線程程序,就可能出現問題。而有序性就是防止指令重排序帶來的問題。併發
一、內存模型產生緣由性能
Java存在一個線程可見性的問題,是因爲Java內存模型的緣由。
Java是跨平臺語言,能夠支持不一樣的硬件平臺,這是Java虛擬機的功勞。Java內存模型(Java Memory Model,JMM)是Java虛擬機規範定義的,用來屏蔽掉Java程序在各類不一樣硬件和操做系統對內存訪問的差別,這樣就能夠實現Java程序在各類不一樣的硬件平臺上都能達到內存訪問的一致性。能夠避免像c、c++等直接使用物理硬件和操做系統的內存模型在不一樣操做系統和硬件平臺下的不兼容。好比有些c/c++程序在windows平臺運行正常,而在linux平臺運行就會出問題。this
二、內存模型概念spa
雖然Java程序運行在Java虛擬機上面,內存等是虛擬機的一部分,但實際也是使用的物理機的,只不過是Java虛擬機屏蔽了底層硬件細節,統一作了處理。
Java內存模型的主要目標是定義程序中變量的訪問規則,即在Java虛擬機中將變量存儲到主內存或將變量從主內存中取出這樣的底層細節。須要注意的是這裏的變量跟Java程序中的變量不是徹底等同的。這裏的變量是指實例字段,靜態字段,構成數組對象的元素,可是不包括線程內部的局部變量和方法參數,由於這是線程私有的。
這裏能夠簡單的認爲,主內存是Java虛擬機內存區域中的堆,局部變量和方法參數是在虛擬機棧中定義的。可是在堆中的變量若是在多線程中使用,就涉及到了堆和不一樣虛擬機棧中變量的值的一致性問題了。
Java內存模型概念:
主內存:Java虛擬機規定全部的變量都必須在主內存中產生,爲了方便理解,能夠認爲是堆區。與前面說的物理機的主內存相比,物理機的主內存是整個機器的內存,而虛擬機的主內存是虛擬機內存中的一部分。
工做內存:Java虛擬機中每一個線程都有本身的工做內存,該內存是線程私有的,爲了方便理解,能夠認爲是虛擬機棧。線程的工做內存保存了線程須要的變量在主內存中的副本。
Java虛擬機規定,線程對主內存共享變量的操做必須在線程的各自的工做內存中進行,不能直接讀寫主內存中的變量。不一樣的線程之間也不能相互訪問對方的工做內存。若是線程之間須要傳遞變量的值,必須經過主內存來做爲中介進行傳遞。這裏須要說明一下,Java內存模型是一個抽象概念,其實並不存在,它描述的是一種規範。相似下圖
一、volatile
volatile關鍵字修飾的變量能夠保證可見性和有序性。volatile類型的變量在修改後會當即同步到主內存,在使用的時候會從主內存中從新讀取,是依賴主內存爲中介來保證多線程下變量對其餘線程的可見性的。而volatile禁止了指令重排序,從而保證指令的有序性。
線程訪問volatile變量時,可以獲取到最新值,但volatile變量並不能用於線程同步。
二、volatile和synchronized的區別
synchronized用於線程同步,可是和volatile不同,synchronized能夠保證原子性、可見性和有序性。
一、狀態標識
好比用一個變量做爲狀態標識來控制多個線程協同工做。每一個線程經過判斷狀態標識的值來肯定是否執行相關代碼。那這個狀態標識的變量就可使用volatile關鍵字修飾,以確保當某個線程更新了變量的值以後,其餘的線程能夠當即得到最新的值。
二、一個線程寫,多個線程讀
volatile很適用一個線程寫,多個線程讀的場合。好比,相似發佈訂閱的機制,當一個寫線程更新volatile變量值的話,其餘讀線程能夠當即獲取到最新值。
三、volatile和synchronized實現「低開銷讀-寫鎖」
以下顯示的線程安全的計數器,使用 synchronized 確保增量操做是原子的,並使用 volatile 保證當前結果的可見性。若是更新不頻繁的話,該方法可實現更好的性能,由於讀路徑的開銷僅僅涉及 volatile 讀操做,這一般要優於一個無競爭的鎖獲取的開銷。
public class CheesyCounter { private volatile int value; //讀操做,無需synchronized,提升性能 public int getValue() { return value; } //寫操做,必須synchronized。由於x++不是原子操做 public synchronized int increment() { return value++; } }
參考文章:
https://www.jianshu.com/p/5584600d2569https://www.jianshu.com/p/15106e9c4bf3