一、volatile概念
談到volatile,理解原子性和易變性是不同的概念這一點很重要,volatile是輕量級的鎖,它只具備可見性,但沒有原子特性。如果你將一個域聲明為volatile,那么只要對這個域產(chǎn)生了寫操作,所有的讀操作都可以看到這個修改,即便使用了本地緩存也一樣,volatile會被立即寫入到主內(nèi)存中,而讀的操作就發(fā)生在主內(nèi)存中。在非volatile域上的原子操作不必刷新到主內(nèi)存中,所以讀操作的任務(wù)看不到這個值,如果多個任務(wù)在同時訪問某個域,那么這個域就應(yīng)該是volatile,否則這個域就應(yīng)該經(jīng)過同步來訪問,同步也會導(dǎo)致向主內(nèi)存中刷新。
使用volatile而不是synchronize的唯一安全的情況就是類中只有一個可變的域。個人認(rèn)為,第一選擇應(yīng)該是synchronize,這應(yīng)該最安全的方式 。如果并發(fā)水平不高,最好還是不要使用。
二、volatile變量的使用
在使用volatile變量時,應(yīng)當(dāng)考慮是否滿足下面這樣的要求:
對變量的寫入操作不依賴變量的當(dāng)前值
說白了volatile 變量不能用作線程安全計數(shù)器,類似于i++這種增量操作。增量操作符++不是原子的。這個操作分解開來看是先從堆內(nèi)存中獲得i值的副本放到緩存中,然后對副本值加1,最后再將副本值寫回到堆內(nèi)存的變量i中,是一個由讀取-修改-寫入操作序列組成的組合操作。
沒有用于其它變量的不變式條件中(lower小于upper)
/*
* volatile只保證lower與upper的最后寫入一定會被其它讀取的線程看到
* 但不能保證在lower或upper寫入時,另一個變量的值沒有發(fā)生變化
*/
private volatile int lower;
private volatile int upper;
public int getLower() {
return lower;
}
public int getUpper() {
return upper;
}
public void setLower(int lower) {
if (lower 》 upper)
throw new IllegalArgumentException();
this.lower = lower;
}
public void setUpper(int upper) {
if (upper 《 lower)
throw new IllegalArgumentException();
this.upper = upper;
}1234567891011121314151617181920212223242526
volatile變量就不適合用于不變性條件這種情況,以上下限為例,lower必須小于upper,這就是一種不變性條件,你可以理解為這是一種規(guī)則限制。它們都只有set與get方法,但在set方法里面加入了約束條件,這時,volatile的可見性就不能保證并發(fā)時,lower與upper之間的不變性條件(lower小于upper)一定成立了。
曾經(jīng)見到過這樣的一個面試題:
volatile 能使得一個非原子操作變成原子操作嗎?
答案是能的,在基本數(shù)據(jù)類型中l(wèi)ong和double是非原子性的,double 和 long 都是64位寬,因此對這兩種類型的讀是分為兩部分的,第一次讀取第一個 32 位,然后再讀剩下的 32 位,如果一個線程正在修改該 long 變量的值,另一個線程可能只能看到該值的一半(前 32 位)。但是將long與double加上volatile ,對變量的讀寫是原子性的
三、volatile的作用
volatile不是保護(hù)線程安全的。它保護(hù)的是變量安全。主要的功能是保護(hù)變量不被主函數(shù)和中斷函數(shù)反復(fù)修改造成讀寫錯誤。
volatile具備兩種特性:
保證此變量對所有線程的可見性,指一條線程修改了這個變量的值,新值對于其他線程來說是可見的,但并不是多線程安全的。
禁止指令重排序優(yōu)化。
Volatile和Synchronized四個不同點:
1 粒度不同,后者鎖對象和類,前者針對變量
2 syn阻塞,volatile線程不阻塞
3 syn保證三大特性,volatile不保證原子性
4 syn編譯器優(yōu)化,volatile不優(yōu)化
評論
查看更多