併發程式設計——為什麼volatile變數++操作執行緒不安全
學習volatile的時候也許我們會看到下面這句話:
對於volatile變數來說,自增操作執行緒不安全。 |
那為什麼不安全呢?本帥博主看的是《併發程式設計的藝術》這本書,這本書對這一事件也沒有做出很讓人易懂的解釋。那麼我們自己用例子來測試一下咯~
測試程式碼如下:
package xiancheng; public class volatileTest { public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub VolatileExample example=new VolatileExample(); Thread thread1=new Thread() { public void run(){ for(int i=0;i<100000;i++) { example.getAndIncrement(); } } }; Thread thread2=new Thread() { public void run(){ for(int i=0;i<100000;i++) { example.getAndIncrement(); } } }; thread1.start(); thread2.start(); Thread.sleep(6000);//等一會,讓執行緒都執行完。 System.out.println(example.get()); } } class VolatileExample{ volatile int v=0; public void set(int l) { v=l; } public void getAndIncrement() { v++; } public int get() { return v; } }
在上面這個程式碼中,VolatileExample類彙總聲明瞭一個volatile變數v,以及三個方法,set()、get()和用來進行自增操作的getAndIncrement()方法。
在主函式中,我建立了兩個執行緒,各自迴圈呼叫VolatileExample類的例項volatileExample中的getAndIncrement()方法100000次。
如果volatile保證++安全,那麼執行結果將是200000.
那麼真實結果是多少呢?
多執行幾次:
從上面幾次的執行結果我們可以看到,始終都不到200000萬。這樣的結果表明volatile不保證volatile++這樣的操作具有原子性。
為什麼呢?
其實很簡單,因為“++”屬於複合操作。
上面的VolatileExample其實等價於一下的程式碼:
class VolatileExample{
int v=0;
public synchronized void set(int l) {
v=l;
}
public void getAndIncrement() {
int temp=get();
temp+=1;
set(temp);
}
public synchronized int get() {
return v;
}
}
如果不清晰的話,我們可以通過javap反編譯一下。
使用javap對VolatileExample類進行反編譯,出來的結果如下:
我們可以看到,getAndIncrement()方法中的v++語句被編譯成了 七條語句,這屬於複合操作。
v++其實相當於:
- 讀v
- 對v+1;
- 將原來的v值置為v+1。
volatile保證可見性,當進行++操作的時候,volatile保證第一條指令正確,即讀正確。當執行接下來的指令的時候,其他執行緒可能對v加大了,當將v存回去的時候(即執行putfield指令的時候),可能將一個更小的v同步回主記憶體去了。所以最終得到的數字就會小於200000.
總結:volatile的讀寫具有原子性,但是自增操作屬於複合操作,因此不具有原子性,所以執行緒也不安全。
好啦,以上就是關於volatile變數自增是否執行緒安全的相關知識總結,如果大家有什麼不明白的地方或者發現文中有描述不好的地方,歡迎大家留言評論,我們一起學習呀。
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!