notify用法 - java thread wait



為什麼必須等待()始終處於同步塊中 (6)

如果可以在同步塊外調用wait() ,保留它的語義,掛起調用者線程有什麼潛在的危害?

讓我們來說明如果wait()可以在具有一個具體示例的同步塊之外調用,那麼我們會遇到什麼問題。

假設我們要實現一個阻塞隊列(我知道,在API中已經有一個了:)

第一次嘗試(沒有同步)可以沿著下面的線看起來

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}

這是可能發生的事情:

  1. 消費者線程調用take()並看到buffer.isEmpty()

  2. 在消費者線程繼續調用wait() ,生產者線程出現並調用完整的give() ,即buffer.add(data); notify(); buffer.add(data); notify();

  3. 消費者線程現在將調用wait() (並錯過剛剛調用的notify() )。

  4. 如果不幸,生產者線程將不會產生更多的give()因為消費者線程永不會被喚醒,並且我們有一個死鎖。

一旦你理解了這個問題,解決方案是顯而易見的:始終執行give / notifyisEmpty / wait原子。

沒有進入細節:這個同步問題是普遍的。 正如Michael Borgwardt所指出的,wait / notify是關於線程之間的通信,所以你總是會得到類似於上面描述的競爭狀態。 這就是為什麼強制執行“僅在等待內同步”規則。

coding.derkeiler.com/Archive/Java/comp.lang.java.programmer/…coding.derkeiler.com/Archive/Java/comp.lang.java.programmer/…總結了這一段:

您需要絕對保證服務員和通知者對謂詞的狀態達成一致。 服務員在進入睡眠之前稍微檢查一下謂詞的狀態,但是它在判斷為正確時取決於睡眠時的正確性。 這兩個事件之間存在一段時間的漏洞,可能會破壞程序。

生產者和消費者需要達成一致的謂詞在上面的例子buffer.isEmpty() 。 協議通過確保等待和通知在synchronized塊中執行來解決。

這篇文章在這裡被重寫為一篇文章: Java:為什麼等待必須在同步塊中調用

https://src-bin.com

我們都知道,為了調用Object.wait() ,這個調用必須放在synchronized塊中,否則拋出IllegalMonitorStateException異常。 但是,這個限制的理由是什麼? 我知道wait()釋放監視器,但為什麼我們需要通過使特定塊同步來顯式獲取監視器,然後通過調用wait()釋放監視器?

如果可以在同步塊外調用wait() ,保留它的語義,掛起調用者線程有什麼潛在的危害?


Answer #1

@滾球是正確的。 wait()被調用,所以當這個wait()調用發生時,線程可以等待一些條件發生,線程被迫放棄它的鎖。
放棄一些東西,你需要先擁有它。 線程需要先擁有鎖。 因此需要在synchronized方法/塊中調用它。

是的,如果您沒有在synchronized方法/程序塊中檢查條件,我同意所有上述有關潛在損害/不一致情況的答案。 然而,正如@ shrini1000指出的那樣,只是在synchronized塊中調用wait()不會避免這種不一致的發生。

這是一個很好的閱讀..


Answer #2

如果您在wait()之前同步,則可能導致的問題如下所示:

  1. 如果第一個線程進入makeChangeOnX()並檢查while條件,並且它是truex.metCondition()返回false ,意味著x.conditionfalse ),所以它會進入它。 然後,在wait()方法之前,另一個線程進入setConditionToTrue()並將x.condition設置為true並將notifyAll()
  2. 那麼只有在那之後,第一個線程才會進入他的wait()方法(不會受到前幾秒發生的notifyAll()影響)。 在這種情況下,第1個線程將繼續等待另一個線程執行setConditionToTrue() ,但這可能不會再發生。

但是如果你在改變對象狀態的方法之前進行synchronized ,這不會發生。

class A {

    private Object X;

    makeChangeOnX(){
        while (! x.getCondition()){
            wait();
            }
        // Do the change
    }

    setConditionToTrue(){
        x.condition = true; 
        notifyAll();

    }
    setConditionToFalse(){
        x.condition = false;
        notifyAll();
    }
    bool getCondition(){
        return x.condition;
    }
}

Answer #3

我們都知道wait(),notify()和notifyAll()方法用於線程間通信。 為了擺脫錯過的信號和虛假的喚醒問題,等待線程總是等待一些條件。 例如-

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

然後通知線程集wasNotified變量為true並通知。

每個線程都有其本地緩存,因此所有更改都先寫入,然後逐漸提升到主內存。

如果在同步塊中不調用這些方法,那麼wasNotified變量將不會刷新到主內存中,並且會存在於線程的本地緩存中,因此等待的線程將繼續等待信號,儘管它通過通知線程進行了重置。

為了解決這些類型的問題,這些方法總是在同步塊內調用,以確保當同步塊啟動時,所有內容都將從主內存中讀取,並在退出同步塊之前刷新到主內存中。

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}

謝謝,希望澄清。


Answer #4

直接從this java的oracle教程:

當線程調用d.wait時,它必須擁有d的內部鎖 - 否則會引發錯誤。 在同步方法內調用等待是獲取內部鎖定的簡單方法。


Answer #5

這基本上與硬件架構(即RAM緩存 )有關。

如果不同時使用wait()notify() ,則另一個線程可能會進入同一個塊,而不是等待顯示器輸入。 而且,當例如訪問一個沒有同步塊的數組時,另一個線程可能不會看到它的變化...實際上,另一個線程在x級緩存中已經擁有該數組的副本 不會看到任何變化即第一/第二/第三級高速緩存)的線程處理CPU內核。

但是同步塊只是獎牌的一面:如果實際上從非同步上下文訪問同步上下文中的對象,即使在同步塊內,對像也不會同步,因為它擁有自己的副本對像在其緩存中。 我在這裡寫了關於這個問題: https://.com/a/21462631 : https://.com/a/21462631 當一個鎖持有一個非最終對象時,對象的引用是否仍然被另一個線程改變?

此外,我確信x級緩存對大多數不可重現的運行時錯誤負責。 這是因為開發人員通常不會學習低層次的東西,比如CPU如何工作或者內存層次結構如何影響應用程序的運行: http://en.wikipedia.org/wiki/Memory_hierarchy : http://en.wikipedia.org/wiki/Memory_hierarchy

它仍然是一個謎題,為什麼編程類首先不以內存層次結構和CPU架構開始。 “你好世界”在這裡不會有幫助。 ;)





wait