2016-11-22 42 views
1

我想知道例如r.wait()的作品?有了這個代碼:如何通過notify/wait()喚醒/暫停特定(一組)線程?

public class Buffer1<T> { 
private T content; 
private boolean empty; 
private Object r = new Object(); 
private Object w = new Object(); 

public Buffer1() { 
empty = true; } 

public Buffer1(T content) { 
this.content = content; 
empty = false; } 

public T take() throws InterruptedException { 
synchronized (r) { 
while (empty) { 
r.wait(); 
} 

synchronized (w) { 
empty = true; 
w.notify(); 
return content; 
    } 
} 
} 

public void put(T o) throws InterruptedException { 
synchronized(w) { 
while (!empty) { 
w.wait(); 
} 

synchronized (r) { 
empty = false; 
r.notify(); 
content = o; 
} 

如何r.wait(),w.wait(),r.notify(),w.notify()工作?它們如何與同步(r)/同步(w)一起工作?

+0

那麼,你的生活可能會更簡單'notifyAll()'...它不是真的應該喚醒一組特定的線程。它喚醒了所有線程。這就是爲什麼'wait()'總是出現在循環內部的原因。一旦被喚醒,線程本身應該檢查是否現在是喚醒的好時機,如果沒有,則返回'wait()'。但喚醒和通知是併發編程的低層次結構,你真的應該考慮使用Executor Framework來簡化併發模塊的組合。 – scottb

+0

或java.util.concurrent包中的信號量。 – EJP

回答

0

線程不會被掛起。會發生什麼情況是一個線程進入一個同步塊或方法,獲取鎖(這裏是r或w),並且如果線程調用等待其鎖獲取的對象,則線程被掛起,釋放它稱爲等待的鎖,以及被添加到該鎖的等待集。

這裏有一個模式,包含一個等待呼叫的循環。調用該方法的線程必須一直等待,直到循環中的測試爲假。讓線程從等待狀態開始的狀態稱爲條件。在循環中調用wait方法主要是因爲通知的線程沒有鎖的所有權,所以需要在重新獲取鎖之後測試當前狀態。

您可以通過調用該鎖的notifyAll來喚醒鎖的等待集中的所有線程。在實踐中,這不是最優的,因爲通常只有一個線程可以獲得鎖定並一次取得進展。當線程競爭同一個鎖可以等待不同的條件並且通知可能是針對與某些線程無關的狀態時,使用notifyAll會發生。如果使用通知,那麼只有一個線程(在調度程序中突然選擇)被喚醒。如果線程正在等待的條件不是通知的條件,則通知將丟失,並且線程無法進展。如果通知適用於任何線程,則使用no​​tifyAll,然後其中一個線程可以取得進展。哪一個勝過另一種選擇,即使其代價是所有其他正在等待的線程獲得上下文切換並返回等待狀態。

在發佈代碼中,意圖似乎是通過爲每個條件分別設置一個鎖對象來避免使用notifyAll。對象r有線程在等待,直到緩衝區不爲空,對象w有線程在等待,直到緩衝區爲空。這樣,當通知被調用時,它肯定會喚醒一個與通知相關的線程(只有等待放置的線程可以被w.notify()喚醒)。

該代碼的問題在於put和take操作獲取兩個鎖,並且它們以相反的順序獲取它們。這是一個引起僵局的好方法。使用synchronized關鍵字和內部鎖定無法超時和退出,因此沒有好的恢復方法。一旦你有一個線程有r並且想要w,而另一個線程有w並且想要r的情況,那麼你就被卡住了。每個持有鎖的兩個線程都無法進展,其他任何線程都無法鎖定,導致每個線程嘗試輸入此緩衝區的一個方法,直到您終止JVM。