在java中,每一個線程有一塊工做內存區,其中存放這被全部線程共享的主內存中變量值的拷貝。當線程執行時,它在本身的工做內存中操做這些變量。爲了獲取一個共享變量,一個線程先獲取鎖定並清除它的工做內存區,這就保證了該共享變量從全部的線程的共享主內存區正確的裝入到線程的工做內存區,當線程解鎖時保證該工做內存區的變量的值寫回到共享主內存區。java
線程工做內存和主內存的交互圖以下:緩存
從上圖中能夠看出,主內存和線程工做內存間的數據傳輸與線程工做內存和線程執行有必定的時間間隔,並且每次所消耗的時間可能還不相同,這樣就存在線程操做的數據的不一致性。因爲每一個線程都有本身的線程工做內存,所以當一個線程改變本身工做內存中的數據的時候,對於其餘系統來講多是不可見的。所以,使用volatile關鍵字迫使全部的線程均讀寫主內存中對應的變量,從而使得volatile關鍵字修飾的變量在多線程間可見。多線程
volatile修飾的變量,jvm虛擬機只是保證從主內存加載到線程工做內存的值是最新的。app
聲明爲volatile的變量具備以下特性:jvm
一、其餘線程對變量的修改能夠即時反映在當前線程中。ide
二、確保當前線程對volatile變量的修改,能即時的寫回到共享主內存中,並被其餘線程所見。性能
三、使用volatile修飾的變量,編譯器會保證其有序性。測試
用在多線程,同步變量。 線程爲了提升效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。只在某些動做時才進行A和B的同步。所以存在A和B不一致的狀況。volatile就是用來避免這種狀況的。volatile告訴jvm, 它所修飾的變量不保留拷貝,直接訪問主內存中的(也就是上面說的A) atom
下面一個測試例子:線程
public class MyThread extends Thread{ private volatile boolean stop = false;//確保stop在多線程中可見 public void stopMe(){ stop = true; System.out.println("stopMe"+System.currentTimeMillis()); } @Override public void run() { int i = 0; while(!stop){ i++; } System.out.println("run"+System.currentTimeMillis()); System.out.println("stop thread"); } }
若是stop沒有被聲明爲volatile類型,那麼線程在執行run的時候是檢查本身的工做內存的副本,不能得知其餘線程對stop的修改,所以線程沒法結束。可是將stop被聲明爲volatile類型,那麼在其餘線程修改stop後,線程會馬上知道,則會跳出循環,正常結束。
volitile與synchronized的區別
Volatile通常狀況下不能代替sychronized,由於volatile不能保證操做的原子性,即便只是i++,實際上也是由多個原子操做組成:read i; inc; write i,假如多個線程同時執行i++,volatile只能保證他們操做的i是同一塊內存,但依然可能出現寫入髒數據的狀況。若是配合Java 5增長的atomic wrapper classes,對它們的increase之類的操做就不須要sychronized。
synchronized得到並釋放監視器——若是兩個線程使用了同一個對象鎖,監視器能強制保證代碼塊同時只被一個線程所執行。volatile只是在線程內存和「主」內存間同步某個變量的值,而synchronized經過鎖定和解鎖某個監視器同步全部變量的值。顯然synchronized要比volatile消耗更多資源。
在使用volatile關鍵字時要慎重,並非只要簡單類型變量使用volatile修飾,對這個變量的全部操做都是原來操做,當變量的值由自身的上一個決定時,如n=n+一、n++ 等,volatile關鍵字將失效,只有當變量的值和自身上一個值無關時對該變量的操做纔是原子級別的,如n = m + 1,這個就是原級別的。因此在使用volatile關鍵時必定要謹慎,若是本身沒有把握,可使用synchronized來代替volatile。 volatile 變量不會像鎖那樣形成線程阻塞,在某些狀況下,若是讀操做遠遠大於寫操做,volatile 變量還能夠提供優於鎖的性能優點。
若是對聲明瞭Volatile變量進行寫操做,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。可是就算寫回到內存,若是其餘處理器緩存的值仍是舊的,再執行計算操做就會有問題,因此在多處理器下,爲了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每一個處理器經過嗅探在總線上傳播的數據來檢查本身緩存的值是否是過時了,當處理器發現本身緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操做的時候,會強制從新從系統內存裏把數據讀處處理器緩存裏。
lock前綴指令實際上至關於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經所有完成;
2)它會強制將對緩存的修改操做當即寫入主存;
3)若是是寫操做,它會致使其餘CPU中對應的緩存行無效。
把對volatile變量的單個讀/寫,當作是使用同一個監視器鎖對這些單個讀/寫操做作了同步。下面咱們經過具體的示例來講明,請看下面的示例代碼:
class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile聲明64位的long型變量 public void set(long l) { vl = l; //單個volatile變量的寫 } public void getAndIncrement () { vl++; //複合(多個)volatile變量的讀/寫 } public long get() { return vl; //單個volatile變量的讀 } }
假設有多個線程分別調用上面程序的三個方法,這個程序在語意上和下面程序等價:
class VolatileFeaturesExample { long vl = 0L; // 64位的long型普通變量 public synchronized void set(long l) { //對單個的普通 變量的寫用同一個監視器同步 vl = l; } public void getAndIncrement () { //普通方法調用 long temp = get(); //調用已同步的讀方法 temp += 1L; //普通寫操做 set(temp); //調用已同步的寫方法 } public synchronized long get() { //對單個的普通變量的讀用同一個監視器同步 return vl; } }
synchronized得到並釋放監視器——若是兩個線程使用了同一個對象鎖,監視器能強制保證代碼塊同時只被一個線程所執行——這是衆所周知的事實。可是,synchronized也同步內存:事實上,synchronized在「 主」內存區域同步整個線程的內存。
volatile只是在線程內存和「主」內存間同步某個變量的值,而synchronized經過鎖定和解鎖某個監視器同步全部變量的值。顯然synchronized要比volatile消耗更多資源。
Volatile只能保證可見性和有序性,對任意單個volatile變量的讀/寫具備原子性,全部在對volatile變量進行操做的時候要保證其操做是原子性,不然就要加鎖來保證原子性。
一、對變量的寫操做不依賴於當前值。
二、該變量沒有包含在具備其餘變量的不變式中。