在沒用同步的情況下共享變量
public class MyVolatite {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
@Override
public void run() {
System.out.println("線程開始執行" + ready);
while (!ready) {
Thread.yield();
}
//正常這裏會輸出42,但是發生重排序之後,ready = true;反而先執行,從而導致這裏輸出0
//我自己測試了好多遍,這種情況不容易出現
System.out.println(number);
}
}
public static void main(String[] args) throws InterruptedException {
new ReaderThread().start();
number = 42;
ready = true;
}
}
運行結果:
線程開始執行true
42
這裏可能會發生兩種情況(發生概率很低,我在測試的時候執行了很多次都沒有出現)
1、ReaderThread可能會一直執行,因爲讀線程可能永遠都看不到ready的值
2、System.out.println(number);可能輸出0(這種稱作重排序)
number = 42;
ready = true;
可能會發生重排序、首先執行ready=true;
備註(重排序):
在沒有同步的情況下,編譯器、處理器以及運行時等都有可能對操作的執行順序進行一些意向不到的調整。在缺乏足夠同步的多線程程序中,想要對內存操作的執行順序進行判斷,幾乎無法得出正確的結論; |
解決重排序:使用內置鎖可以確保以一種可預測的方式執行
volatile:修飾變量,該變量上的操作與不會與其他內存操作一起重排序、適合於一寫多讀場景
使用volatile解決上面出現的循環執行問題,將ready變量設置爲volatile類型,while (!ready) {每次判斷的時候都會獲取到內存中最新的ready值,所以這裏就不會發生死循環
public class MyVolatite {
private volatile static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
@Override
public void run() {
System.out.println("線程開始執行" + ready);
while (!ready) {
Thread.yield();
}
//正常這裏會輸出42,但是發生重排序之後,ready = true;反而先執行,從而導致這裏輸出0
//我自己測試了好多遍,這種情況不容易出現
System.out.println(number);
}
}
public static void main(String[] args) throws InterruptedException {
new ReaderThread().start();
number = 42;
ready = true;
}
}
下面是一個錯誤的使用volatile的示例,多個線程寫,造成數據混亂
注:兩個線程同時修改volatile變量,導致結果產生了偏差;所以volatile只能保證可見性(每次讀到的是最新值),不能保證原子性
public class MyNotSafe {
private static volatile int sum = 0 ;
private void incrSum(){
for (int i = 0; i < 1000; i++) {
sum++;
}
System.out.println(sum);
}
public static void main(String[] args) {
MyNotSafe myNotSafe = new MyNotSafe();
Thread t1 = new Thread(()->myNotSafe.incrSum());
Thread t2= new Thread(()->myNotSafe.incrSum());
t1.start();
t2.start();
}
}
圖解volatile可見性
https://www.cnblogs.com/bbgs-xc/p/12725426.html