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