用在多線程,同步變量卷玉。 線程為了提高效率哨颂,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B相种。只在某些動作時才進行A和B的同步咆蒿。因此存在A和B不一致的情況。volatile就是用來避免這種情況的蚂子。volatile告訴jvm, 它所修飾的變量不保留拷貝缭黔,直接訪問主內(nèi)存中的(也就是上面說的A)
在Java內(nèi)存模型中食茎,有main memory,每個線程也有自己的memory (例如寄存器)馏谨。為了性能别渔,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現(xiàn)同一個變量在某個瞬間惧互,在一個線程的memory中的值可能與另外一個線程memory中的值哎媚,或者main memory中的值不一致的情況。
一個變量聲明為volatile喊儡,就意味著這個變量是隨時會被其他線程修改的拨与,因此不能將它cache在線程memory中。以下例子展現(xiàn)了volatile的作用:
public class StoppableTask extends Thread {
private volatile boolean pleaseStop;
public void run() {
while (!pleaseStop) {
// do some stuff...
}
}
public void tellMeToStop() {
pleaseStop = true;
}
}
假如pleaseStop沒有被聲明為volatile艾猜,線程執(zhí)行run的時候檢查的是自己的副本买喧,就不能及時得知其他線程已經(jīng)調(diào)用tellMeToStop()修改了pleaseStop的值捻悯。
Volatile一般情況下不能代替sychronized,因為volatile不能保證操作的原子性淤毛,即使只是i++今缚,實際上也是由多個原子操作組成:read i; inc; write i,假如多個線程同時執(zhí)行i++低淡,volatile只能保證他們操作的i是同一塊內(nèi)存姓言,但依然可能出現(xiàn)寫入臟數(shù)據(jù)的情況。如果配合Java 5增加的atomic wrapper classes蔗蹋,對它們的increase之類的操作就不需要sychronized何荚。
Reference:
http://www.javamex.com/tutorials/synchronization_volatile.shtml
http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
恐怕比較一下volatile和synchronized的不同是最容易解釋清楚的。volatile是變量修飾符纸颜,而synchronized則作用于一段代碼或方法兽泣;看如下三句get代碼:
int i1;
int geti1() {return i1;}
volatile int i2;
int geti2()
{return i2;}
int i3;
synchronized int geti3() {return i3;}
geti1()
得到存儲在當(dāng)前線程中i1的數(shù)值。多個線程有多個i1變量拷貝胁孙,而且這些i1之間可以互不相同唠倦。換句話說,另一個線程可能已經(jīng)改變了它線程內(nèi)的i1值涮较,而這個值可以和當(dāng)前線程中的i1值不相同稠鼻。事實上,Java有個思想叫“主”內(nèi)存區(qū)域狂票,這里存放了變量目前的“準(zhǔn)確值”候齿。每個線程可以有它自己的變量拷貝,而這個變量拷貝值可以和“主”內(nèi)存區(qū)域里存放的不同闺属。因此實際上存在一種可能:“主”內(nèi)存區(qū)域里的i1值是1慌盯,線程1里的i1值是2,線程2里的i1值是3——這在線程1和線程2都改變了它們各自的i1值掂器,而且這個改變還沒來得及傳遞給“主”內(nèi)存區(qū)域或其他線程時就會發(fā)生亚皂。
而 geti2()得到的是“主”內(nèi)存區(qū)域的i2數(shù)值。用volatile修飾后的變量不允許有不同于“主”內(nèi)存區(qū)域的變量拷貝国瓮。換句話說灭必,一個變量經(jīng) volatile修飾后在所有線程中必須是同步的;任何線程中改變了它的值乃摹,所有其他線程立即獲取到了相同的值禁漓。理所當(dāng)然的,volatile修飾的變量存取時比一般變量消耗的資源要多一點孵睬,因為線程有它自己的變量拷貝更為高效播歼。
既然volatile關(guān)鍵字已經(jīng)實現(xiàn)了線程間數(shù)據(jù)同步,又要 synchronized干什么呢掰读?呵呵荚恶,它們之間有兩點不同撩穿。首先,synchronized獲得并釋放監(jiān)視器——如果兩個線程使用了同一個對象鎖谒撼,監(jiān)視器能強制保證代碼塊同時只被一個線程所執(zhí)行——這是眾所周知的事實食寡。但是,synchronized也同步內(nèi)存:事實上廓潜,synchronized在“ 主”內(nèi)存區(qū)域同步整個線程的內(nèi)存抵皱。因此,執(zhí)行g(shù)eti3()方法做了如下幾步:
- 線程請求獲得監(jiān)視this對象的對象鎖(假設(shè)未被鎖辩蛋,否則線程等待直到鎖釋放)
- 線程內(nèi)存的數(shù)據(jù)被消除呻畸,從“主”內(nèi)存區(qū)域中讀入(Java虛擬機能優(yōu)化此步。悼院。伤为。[后面的不知道怎么表達(dá),汗])
- 代碼塊被執(zhí)行
- 對于變量的任何改變現(xiàn)在可以安全地寫到“主”內(nèi)存區(qū)域中(不過geti3()方法不會改變變量值)
- 線程釋放監(jiān)視this對象的對象鎖
因此volatile只是在線程內(nèi)存和“主”內(nèi)存間同步某個變量的值,而synchronized通過鎖定和解鎖某個監(jiān)視器同步所有變量的值据途。顯然synchronized要比volatile消耗更多資源绞愚。
volatile關(guān)鍵字相信了解Java多線程的讀者都很清楚它的作用。volatile關(guān)鍵字用于聲明簡單類型變量颖医,如int位衩、float、 boolean等數(shù)據(jù)類型熔萧。如果這些簡單數(shù)據(jù)類型聲明為volatile糖驴,對它們的操作就會變成原子級別的。但這有一定的限制佛致。例如贮缕,下面的例子中的n就不是原子級別的:
package mythread;
public class JoinThread extends Thread
{
public static volatile int n = 0 ;
public void run()
{
for ( int i = 0 ; i < 10 ; i ++ )
try
{
n = n + 1 ;
sleep( 3 ); // 為了使運行結(jié)果更隨機,延遲3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[ 100 ];
for ( int i = 0 ; i < threads.length; i ++ )
// 建立100個線程
threads[i] = new JoinThread();
for ( int i = 0 ; i < threads.length; i ++ )
// 運行剛才建立的100個線程
threads[i].start();
for ( int i = 0 ; i < threads.length; i ++ )
// 100個線程都執(zhí)行完后繼續(xù)
threads[i].join();
System.out.println( " n= " + JoinThread.n);
}
}
如果對n的操作是原子級別的俺榆,最后輸出的結(jié)果應(yīng)該為n=1000跷睦,而在執(zhí)行上面積代碼時,很多時侯輸出的n都小于1000肋演,這說明n=n+1不是原子級別的操作。原因是聲明為volatile的簡單變量如果當(dāng)前值由該變量以前的值相關(guān)烂琴,那么volatile關(guān)鍵字不起作用爹殊,也就是說如下的表達(dá)式都不是原子操作:
n = n + 1 ;
n ++ ;
如果要想使這種情況變成原子操作,需要使用synchronized關(guān)鍵字奸绷,如上的代碼可以改成如下的形式:
package mythread;
public class JoinThread extends Thread
{
public static int n = 0 ;
public static synchronized void inc()
{
n ++ ;
}
public void run()
{
for ( int i = 0 ; i < 10 ; i ++ )
try
{
inc(); // n = n + 1 改成了 inc();
sleep( 3 ); // 為了使運行結(jié)果更隨機梗夸,延遲3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[ 100 ];
for ( int i = 0 ; i < threads.length; i ++ )
// 建立100個線程
threads[i] = new JoinThread();
for ( int i = 0 ; i < threads.length; i ++ )
// 運行剛才建立的100個線程
threads[i].start();
for ( int i = 0 ; i < threads.length; i ++ )
// 100個線程都執(zhí)行完后繼續(xù)
threads[i].join();
System.out.println( " n= " + JoinThread.n);
}
}
上面的代碼將n=n+1改成了inc(),其中inc方法使用了synchronized關(guān)鍵字進行方法同步号醉。因此反症,在使用volatile關(guān)鍵字時要慎重辛块,并不是只要簡單類型變量使用volatile修飾,對這個變量的所有操作都是原來操作铅碍,當(dāng)變量的值由自身的上一個決定時润绵,如n=n+1、n++ 等胞谈,volatile關(guān)鍵字將失效尘盼,只有當(dāng)變量的值和自身上一個值無關(guān)時對該變量的操作才是原子級別的,如n = m + 1烦绳,這個就是原級別的卿捎。所以在使用volatile關(guān)鍵時一定要謹(jǐn)慎,如果自己沒有把握径密,可以使用synchronized來代替volatile午阵。