volatile
import java.util.Scanner;
public class ThreadDemo {
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread() {
@Override
public void run() {
while (counter.flag == 0) {
}
System.out.println("循環結束");
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("親輸入一個整數");
counter.flag = scanner.nextInt();
}
};
t2.start();
}
}
這個代碼本來的預期效果是輸入整數後循環結束,但是當輸入後,並沒有結束。
其實,這是編譯器優化的問題。開始,線程1 反覆對循環條件進行比較操作(先從內存中讀取flag的值到CPU,在CPU中比較這個值和0的相等關係),因爲從CPU寄存器上讀取數據比內存中讀數據還是快很多的,編譯器判定這個判斷相等操作就是不斷從內存中讀取內存而已,於是編譯器就進行了優化,在第一次吧數據從內存讀取到CPU後,之後就直接從CPU中讀取數據,所以編譯器認爲flag沒有改動,就出現了誤判。
這樣的優化策略就是“內存可見性”;
如果優化生效,內存不可見,不生效,內存纔是可見的。
但是當在flag前加上volatile後,就可以保證內存可見性了。
但這個和原子性是沒有關係的,對於一個線程讀,一個線程寫,得用volatile解決,但是對於兩個線程修改同一變量時,還得用加鎖去解決。
notify和wait
對象等待集 (本質讓程序員有一定手段去幹預線程調度)
主要還是因爲搶佔式執行的問題
wait方法:當操作條件不成熟就等待(必須在sychronized內部使用)
notify方法:當條件成熟,通知指定線程來工作。(也必須在sychronized裏使用)
wait步驟:
1.釋放鎖
2.等待通知
3.收到通知,重新獲取鎖,繼續往下執行
其中,前兩步在wait中是原子的,避免競態條件問題。
調用鎖的對象和wait的對象必須是對應的。
import java.util.Scanner;
public class ThreadDemo4 {
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait開始");
locker.wait();
System.out.println("wait結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("輸入整數");
int num = scanner.nextInt();
synchronized (locker) {
System.out.println("notify開始");
locker.notify();
System.out.println("notify結束");
}
}
};
t2.start();
}
}