起因
逛【部落格園-博問】時發現了一段有意思的問題:
問題連結:https://q.cnblogs.com/q/140032/
這段程式碼是這樣的:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AutomicityTest implements Runnable {
private int i = 0;
public int getValue() {
return i;
}
/**
* 同步方法,加2
*/
public synchronized void evenIncrement() {
i += 2;
// i++;
// i++;
}
@Override
public void run() {
while (true) {
evenIncrement();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
// AutomicityTest執行緒
AutomicityTest automicityTest = new AutomicityTest();
exec.execute(automicityTest);
// main執行緒
while (true) {
int value = automicityTest.getValue();
if (value % 2 != 0) {
System.out.println(value);
System.exit(0);
}
}
}
}
程式碼很簡單(一開始我沒看清楚問題,還建議樓主去學習下Java語法,尷尬的一批~~~)
簡單說明下程式碼邏輯
一、AutomicityTest類實現了Runnable介面,並實現 run() 方法,run() 方法中有一個 while(true) 迴圈,迴圈體中呼叫了一個 synchronized 修飾的方法 evenIncrement();
二、AutomicityTest類中有一個 int i 成員變數;
三、在 main() 方法中使用執行緒池執行執行緒(直接 new Thread().start() 是一樣的),然後 while(true) 迴圈體中不停列印成員變數 i 的值,如果是奇數就退出虛擬機器。
大家覺得這段程式碼會輸出什麼呢?
沒錯就是死迴圈!!!
有意思的地方來了,如果我把同步方法 evenIncrement() 改為下面這樣:
public synchronized void evenIncrement() {
// i += 2;
i++;
i++;
}
執行結果是退出了虛擬機器!!!
是不是很納悶兒,要是不納悶兒,就不用往下看了 >_<
一起來分析一下
1.首先從方法入口開始,main() 方法當中建立了一個AutomicityTest執行緒 和 本身 main() 所在的main執行緒,所以AutomicityTest執行緒是一個寫執行緒,main執行緒是一個讀執行緒,既然有讀有寫,又是多個執行緒,就涉及到工作記憶體和主存模型,在這裡我就不贅述了。
2.寫執行緒是有synchronized修飾的,但是讀執行緒並沒有,這就導致了讀寫不一致,解決方法就是給 getValue() 加上synchronized,此時執行結果就正常了
3.但是問題還沒完,為什麼 i += 2 死迴圈,而 【兩條】 i++ 卻退出了虛擬機器呢?
位元組碼層面分析
public synchronized void evenIncrement() {
i += 2;
}
// 對應的位元組碼
public synchronized void evenIncrement();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_2
6: iadd
7: putfield #2 // Field i:I
10: return
public synchronized void evenIncrement() {
i++;
i++;
}
// 對應的位元組碼
public synchronized void evenIncrement();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: aload_0
11: dup
12: getfield #2 // Field i:I
15: iconst_1
16: iadd
17: putfield #2 // Field i:I
20: return
i+=2;
位元組碼指令只有一段putfield,沒有執行是偶數,執行了也是偶數,所以會死迴圈。
i++; i++;
位元組碼指令有兩段putfield,由於之前getValue()沒有加synchronized,那麼在執行getValue()的時候,putfield可能沒有執行,可能執行了一次,也可能執行了兩次,沒有執行是偶數,執行一次是奇數,執行兩次是偶數;又因為AutomicityTest執行緒 run() 是 while (true) {} 的,所以它總能執行到奇數,退出虛擬機器。
最後
到此就分析完了,所以該問題的關鍵就是寫操作是原子的,但是讀操作不是,導致讀出來的資料不是最終的。