while迴圈修改條件後無法跳出的疑惑(已解決)

dust1發表於2018-11-12

最近在編寫專案的時候使用while遇到了一個奇怪的問題。我在使用非同步呼叫的時候主執行緒某一個方法需要等待非同步返回才能被呼叫,因此我設定了一個boolean,當非同步返回時修改條件然後在主執行緒的方法中加入while來長時間遍歷以等待非同步返回。

這裡我將程式碼省略只保留主要:

public static void main(String[] args) {
    Task task = new Task();
    new Thread(task).start();
    try {
        Thread.sleep(3000);
    } catch (Exception e) {}
    task.close();
}

class Task implements Runnable {

    private int i = 1;
    
    public void run() {
        while (i == 1){}
        System.out.println("跳出迴圈");
    }
    
    public void close() {
        i = 2;
    }
}
複製程式碼

Task啟動後會在while中死迴圈,主執行緒等待3s後將i修改成2,但是task中的while沒有跳出,即 i == 1 條件還是為true。

其中的while換成for,do-while都是一樣的結果

通過詢問他人,雖然沒有弄明白髮生的原因。但是他提出了一個解決辦法。

在程式中

while(i == 1){}
複製程式碼

會過多佔用CPU,因此使用Thread.yield()來將CPU資源讓步給其他執行緒。當while中加入這個之後就能達到我需要的效果了。

很奇怪

更新: Java記憶體模型規定:

  1. 共享變數必須儲存在主記憶體中
  2. 執行緒有自己的工作記憶體,執行緒只可以操作自己的工作記憶體
  3. 執行緒要操作共享變數,需要從主記憶體中讀取到工作記憶體,修改後需從工作記憶體同步到主記憶體中。

這三點直接就點名了錯誤的原因。解決辦法有兩個:

  1. volatile關鍵字:
    volatile語意:

    • 使用volatile變數時,必須重新從主記憶體載入,並read、load是連續的。
    • 修改volatile變數後,必須立馬同步回主記憶體,並且store、write是連續的。

    缺點:volatile只能保證執行緒的變數可見性。但是它沒有鎖機制,所以無法避免多個執行緒同時訪問公共變數。

    優點:編寫簡單。

  2. synchronized關鍵字
    synchronized語意:

    • 進入同步塊前,先清空工作記憶體的共享變數,再從主記憶體重新載入,同時獲取該共享資源的鎖。
    • 修改後必須先將共享變數同步回主記憶體中才能釋放鎖。

    優點:有加鎖機制,保護共享資源。

補充 - 記憶體協議:

Java記憶體協議規定了8中原子操作:

  1. lock(鎖定):將主記憶體的變數鎖定,為一線個執行緒獨佔。
  2. unlock(解鎖):將lock加的鎖解除。
  3. read(讀取):作用於主記憶體變數,將主記憶體的變數放入暫存器中。
  4. load(載入):作用於工作記憶體,將暫存器中的主記憶體變數傳遞給執行緒的工作記憶體。
  5. use(使用):作用於工作記憶體,將值傳遞給執行緒的程式碼執行引擎。
  6. assign(賦值):作用於工作記憶體,將執行引擎處理返回的值重新賦值給暫存器。
  7. store(存入):將暫存器中的變數傳入主記憶體中。
  8. write(寫入):作用於主記憶體變數,將store傳過來的值寫入到主記憶體的共享變數中。

這些操作都是原子性,但是操作之間不是原子性。

相關文章