最近在讀java併發編程相關的書籍,螞蟻金服團隊的傑做,能夠好好把java併發相關的內容好好研究一下java
要理解volatile和synchronized的區別,首先仍是須要來理解下java的內存模型編程
java內存模型數組
java中,線程之間的通訊是經過共享內存的方式,存儲在堆中的實例域,靜態域以及數組元素均可以在線程間通訊。java內存模型控制一個線程對共享變量的改變什麼時候對另外一個線程可見。緩存
線程間的共享變量存在主內存中,而對於每個線程,都有一個私有的工做內存。工做內存是個虛擬的概念,涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化,總之就是指線程的本地內存。存在線程本地內存中的變量值對其餘線程是不可見的。多線程
若是線程A與線程B之間如要通訊的話,必需要經歷下面2個步驟,如圖所示:
1. 首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去。
2. 而後,線程B到主內存中去讀取線程A以前已更新過的共享變量。 併發
關於volatile變量jvm
因爲java的內存模型中有工做內存和主內存之分,因此可能會有兩種問題:性能
(1)線程可能在工做內存中更改變量的值,而沒有及時寫回到主內存,其餘線程從主內存讀取的數據仍然是老數據優化
(2)線程在工做內存中更改了變量的值,寫回主內存了,可是其餘線程以前也讀取了這個變量的值,這樣其餘線程的工做內存中,此變量的值沒有被及時更新。spa
爲了解決這個問題,可使用同步機制,也能夠把變量聲明爲volatile,volatile修飾的成員變量有如下特色:
(1)每次對變量的修改,都會引發處理器緩存(工做內存)寫回到主內存。
(2)一個工做內存回寫到主內存會致使其餘線程的處理器緩存(工做內存)無效。
基於以上兩點,若是一個字段被聲明成volatile,java線程內存模型確保全部線程看到這個變量的值是一致的。
此外,java虛擬機規範(jvm spec)中,規定了聲明爲volatile的long和double變量的get和set操做是原子的。這也說明了爲何將long和double類型的變量用volatile修飾,就能夠保證對他們的賦值操做的原子性了
關於volatile變量的使用建議:多線程環境下須要共享的變量採用volatile聲明;若是使用了同步塊或者是常量,則沒有必要使用volatile。
java內存模型與synchronized關鍵字
synchronized關鍵字強制實施一個互斥鎖,使得被保護的代碼塊在同一時間只能有一個線程進入並執行。固然synchronized還有另一個 方面的做用:在線程進入synchronized塊以前,會把工做存內存中的全部內容映射到主內存上,而後把工做內存清空再從主存儲器上拷貝最新的值。而 在線程退出synchronized塊時,一樣會把工做內存中的值映射到主內存,但此時並不會清空工做內存。這樣一來就能夠強制其按照上面的順序運行,以 保證線程在執行完代碼塊後,工做內存中的值和主內存中的值是一致的,保證了數據的一致性!
因此由synchronized修飾的set與get方法都是至關於直接對主內存進行操做,不會出現數據一致性方面的問題。
所以,在使用volatile關鍵字時要慎重,並非只要簡單類型變量使用volatile修飾,對這個變量的全部操做都是原來操做,當變量的值由自身的上一個決定時,如n=n+一、n++ 等,volatile關鍵字將失效,只有當變量的值和自身上一個值無關時對該變量的操做纔是原子級別的,如n = m + 1,這個就是原級別的。因此在使用volatile關鍵時必定要謹慎,若是本身沒有把握,可使用synchronized來代替volatile。
總結:volatile本質是在告訴JVM當前變量在寄存器中的值是不肯定的,須要從主存中讀取。能夠實現synchronized的部分效果,但當n=n+1,n++等時,volatile關鍵字將失效,不能起到像synchronized同樣的線程同步的效果。
最後來討論一下volatile的使用場景
synchronized關鍵字是防止多個線程同時執行一段代碼,那麼就會很影響程序執行效率,而volatile關鍵字在某些狀況下性能要優於synchronized,可是要注意volatile關鍵字是沒法替代synchronized關鍵字的,由於volatile關鍵字沒法保證操做的原子性。一般來講,使用volatile必須具有如下2個條件:
1)對變量的寫操做不依賴於當前值
2)該變量沒有包含在具備其餘變量的不變式中
實際上,這些條件代表,能夠被寫入 volatile 變量的這些有效值獨立於任何程序的狀態,包括變量的當前狀態。
事實上,個人理解就是上面的2個條件須要保證操做是原子性操做,才能保證使用volatile關鍵字的程序在併發時可以正確執行。
下面列舉幾個Java中使用volatile的幾個場景。
1.狀態標記
volatile
boolean
flag =
false
;
while
(!flag){
doSomething();
}
public
void
setFlag() {
flag =
true
;
}
2.雙重檢查(Double-Check)
class
Singleton{
private
volatile
static
Singleton instance =
null
;
private
Singleton() {
}
public
static
Singleton getInstance() {
if
(instance==
null
) {
synchronized
(Singleton.
class
) {
if
(instance==
null
)
instance =
new
Singleton();
}
}
return
instance;
}
}