簡體   English   中英

為什么等待/通知/通知所有方法在 java 中不同步?

[英]why wait/notify/notifyAll methods are not synchronized in java ?

在 Java 中,每當我們需要調用 wait/notify/notifyAll 時,我們需要訪問 object 監視器(通過同步方法或通過同步塊)。 所以我的問題是為什么 java 沒有 go 同步等待/通知方法消除了從同步塊或方法調用這些方法的限制。

如果這些被聲明為同步,它將自動獲取監視器訪問權限。

對於 notify 和 notifyAll,您的想法的問題在於,當您通知您時,您通常還會在同一個同步塊中執行其他操作。 因此,使通知方法同步不會給您帶來任何好處,您仍然需要該塊。 同樣,等待必須在同步塊或方法中才能有用,例如在自旋鎖中,無論如何測試都必須同步。 因此,對於您的建議,鎖定的粒度都是錯誤的。

這是一個示例,這是關於 Java 中最簡單的隊列實現:

public class MyQueue<T> {

    private List<T> list = new ArrayList<T>();

    public T take() throws InterruptedException {
        synchronized(list) {
            while (list.size() == 0) {
                list.wait();
            }
            return list.remove(0);
        }
    }

    public void put(T object) {
        synchronized(list) {
            list.add(object);
            list.notify();
        }
    }
}

因此,您可以擁有將內容添加到隊列的生產者線程和取出內容的消費者線程。 當一個線程從隊列中取出某些東西時,它需要在同步塊中檢查列表中是否存在某些內容,一旦收到通知,它需要重新獲取鎖並確保列表中仍然存在某些內容(因為一些其他消費者線程可能已經介入並抓住了它)。還有“虛假喚醒”現象:您不能依靠被喚醒作為發生某事的充分證據,您需要檢查您正在等待的任何條件for 實際上是正確的,這需要在同步塊中完成。

在這兩種情況下,圍繞等待的檢查需要在持有鎖的情況下進行,這樣當代碼根據這些檢查采取行動時,它就知道這些結果當前是有效的。

好問題。 我認為JDK7 Object 實現中的評論對此有所了解(強調我的):

此方法導致當前線程(稱為T )將自己置於此 object 的等待集中,然后放棄此 object 上的任何和所有同步聲明

...

然后線程T以這種通常的方式從等待集中移除,與其他線程一起獲得在 object 上同步的權利; 一旦它獲得了 object 的控制權,它對 object 的所有同步聲明都將恢復到之前的狀態 - 即,恢復到調用wait方法時的狀態 線程T然后從調用wait方法返回 因此,在從wait方法返回時,object 和線程T的同步 state 與調用wait方法時完全相同。

所以我認為要注意的第一點是wait()直到調用者完成等待(顯然)才返回。 這意味着如果wait()本身是同步的,那么調用者將繼續持有 object 上的鎖,並且沒有其他人能夠wait()notify()

現在顯然wait()在幕后做了一些棘手的事情,以迫使調用者失去其對 object 的鎖定的所有權,但如果wait()本身是同步的。

第二點是,如果多個線程在 object 上等待,當使用notify()喚醒其中一個時,標准的爭用方法用於僅允許一個線程在 object 上同步,並且假設wait()將調用者的同步聲明恢復到調用wait()之前的確切 state 。 在我看來,要求調用者在調用wait()之前持有鎖可以簡化這一點,因為它不需要檢查調用者是否應該在wait()返回后繼續持有鎖。 合約規定調用者必須繼續持有鎖,因此簡化了一些實現。

或者也許只是為了避免出現“如果wait()notify()都同步,並且wait()直到notify()被調用時才返回,那么如何成功使用兩者”的邏輯悖論的出現?”。

無論如何,這些是我的想法。

我的猜測是,需要synchronized塊的原因是使用wait()notify()作為synchronized塊中的唯一操作幾乎總是一個錯誤。

Findbugs甚至對此有一個警告,它稱之為“裸通知”。

在我讀過和寫過的所有非錯誤代碼中,它們都在更大的同步塊中使用wait/notify ,涉及讀/寫其他條件

synchronized(lock)
    update condition
    lock.notify()

synchronized(lock)
    while( condition not met)
        lock.wait()

如果wait/notify本身是synchronized的,則不會對所有正確的代碼造成損害(可能會造成小的性能損失); 它對所有正確的代碼也沒有任何好處。

但是,它會允許並鼓勵更多不正確的代碼。

對多線程更有經驗的人應該可以隨意介入,但我相信這會消除同步塊的多功能性。 使用它們的目的是在特定的 object 上進行同步,該 object 用作受監控的資源/信號量。 然后使用等待/通知方法來控制同步塊的執行流程。

請注意,同步方法是在方法持續時間內this進行同步的簡寫(或 class 用於 static 方法)。 同步等待/通知方法本身將消除它們作為線程之間停止/開始信號的使用點。

同步的等待通知 model 要求您先獲取 object 上的監視器,然后再繼續執行任何工作。 它與同步塊使用的互斥 model 不同。

等待通知或相互協作 model 通常用於生產者-消費者場景,其中一個線程產生由另一個線程消費的事件。 編寫良好的實現將努力避免消費者挨餓或生產者用太多事件超出消費者的情況。 為避免這種情況,您將使用等待通知協議,其中

  • 消費者wait生產者產生事件。
  • 生產者產生事件並notifies消費者,然后通常會進入睡眠狀態,直到消費者notified它。
  • 當消費者收到事件通知時,它會醒來,處理該事件並notifies生產者它已完成對事件的處理。

在這種情況下,您可能有許多生產者和消費者。 通過互斥 model 獲取監視器,在waitnotifynotifyAll時必然會銷毀此 model,因為生產者和消費者沒有顯式執行等待。 底層線程將出現在監視器的等待集(由等待通知模型使用)或條目集(由互斥模型使用)中。 調用notifynotifyAll信號線程從等待集移動到監視器的條目集(其中可能存在多個線程之間的監視器爭用,而不僅僅是最近通知的線程)。

現在,當您想使用互斥 model 在waitnotifynotifyAll上自動獲取監視器時,通常表明您不需要使用 wait-notify model。 這是通過推理得出的——您通常只會在一個線程中完成一些工作之后,即在 state 發生更改后,才向其他線程發出信號。 如果您自動獲取監視器並調用notifynotifyAll ,您只是將線程從等待集移動到條目集,程序中沒有任何中間 state,這意味着過渡是不必要的。 很明顯,JVM 的作者意識到了這一點,並沒有將這些方法聲明為同步。

您可以在 Bill Venner 的書 - Inside the Java Virtual Machine中閱讀有關監視器的等待集和入口集的更多信息。

我認為沒有synchronizedwait在某些情況下可以很好地工作。 但它不能用於沒有競爭條件的復雜場景,可能會出現“虛假喚醒”。

代碼適用於隊列。

// producer
give(element){
  list.add(element)
  lock.notify()
}

// consumer
take(){
  obj = null;
  while(obj == null)
    lock.wait()
    obj = list.remove(0) // ignore error indexoutofrange
  return obj
}

這段代碼沒有解釋共享數據的 state,它會忽略最后一個元素,在多線程條件下可能不起作用。 如果沒有競爭條件,則12中的列表可能具有完全不同的狀態。

// consumer
take(){
  while(list.isEmpty()) // 1
    lock.wait()
  return list.remove(0) // 2
}

現在,讓它變得更加復雜和明顯。

指令的執行

  • give(element) lock.notify()->take() lock.wait() resurrected->take() list.remove(0)->rollback(element)
  • give(element) lock.notify()->take() lock.wait() resurrected->rollback(element)->take() list.remove(0)

“虛假喚醒”發生,也使代碼不可預測。

// producer
give(element){
  list.add(element)
  lock.notify()
}
rollback(element){
  list.remove(element)
}

// business code 
produce(element){
   try{
     give(element)
   }catch(Exception e){
     rollback(element) // or happen in another thread
   }
}

// consumer
take(){
  obj = null;
  while(obj == null)
    lock.wait()
    obj = list.remove(0) // ignore error indexoutofrange
  return obj
}

參考 Chris Smith參考 insidevm

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM