2011-12-19 72 views
12

我有一個線程不時更新它的狀態,我想要第二個線程能夠等待第一個線程完成。事情是這樣的:在Java中等待事件 - 它有多困難?

Thread 1: 
    while(true) { 
     ...do something... 
     foo.notifyAll() 
     ...wait for some condition that might never happen... 
     ... 
    } 

Thread 2: 
    ... 
    foo.wait(); 
    ... 

現在,這看起來不錯,所有除非線程1的notifyAll的()之前線程2的wait(),運行在這種情況下,線程2個等待直到線程1通知再次(可能永遠不會發生) 。

我可能的解決方案:

一)我可以使用的CountDownLatch或未來,但兩者有本質上它們只運行一次問題。也就是說,在線程1的while循環中,我需要創建一個新的foo來等待每次,並且線程2需要詢問哪個foo要等待。我對簡單地寫

while(true) { 
    foo = new FutureTask(); 
    ... 
    foo.set(...); 
    ...wait for a condition that might never be set... 
    ... 
} 

一種不好的感覺,因爲我擔心,在富=新FutureTask(),當有人等着老FOO(對於「某些原因」,設置不叫會發生什麼,如異常處理中的錯誤)?

b)或我可以用一個信號:

class Event { 
    Semaphore sem; 
    Event() { sem = new Semaphore(1); sem . } 
    void signal() { sem.release(); } 
    void reset() { sem.acquire(1); } 
    void wait() { if (sem.tryAcquire(1)) { sem.release(); } } 
} 

但我擔心,有一些競爭條件,如果將多個線程wait()就爲它而另外一個信號()S和復位( )秒。

問:

有什麼類似於Windows事件行爲的Java API嗎?或者,如果你鄙視Windows,像golang的WaitGroup(即允許countUp())的CountDownLatch)?什麼?

如何手動做到這一點:

線程2不能簡單地等待,因爲虛假喚醒,並在Java中沒有辦法知道爲什麼的Object.wait()返回。所以我需要一個條件變量來存儲事件是否有信號。線程2:

synchronized(foo) { 
    while(!condition) { 
     foo.wait(); 
    } 
} 

而線程1當然在同步塊中設置條件爲真。感謝週末的提示!

是否有一個包裝該行爲的現有類?

還是我需要複製和粘貼代碼嗎?

+0

什麼問題,你實際上試圖解決?雖然我確信這個問題可以得到解決,但我仍然想問一下,所以我們知道這是您的問題的正確解決方案。這種與線程和信號量有關的問題雖然被很好地理解,但由於小錯誤而容易失敗,因此可能會有更易讀易懂的方法來實現您想要實現的目標。特別是它聽起來像一個簡單的evenlistener模式,聽衆註冊事件的來源會更合適。 – Thor84no 2011-12-19 13:54:42

+0

是的,一個事件監聽器可以通過讓線程1「push」更新來解決這個問題。這通常是優選的方法,例如在服務器環境中。然而,我偶爾碰到線程安全「拉」更新的問題,直到現在我從來沒有一個乾淨的解決方案。 – Kosta 2011-12-19 16:29:33

回答

18

標準做法是在執行notifyAll時更改某些狀態並在執行wait()時檢查某些狀態。

例如

boolean ready = false; 

// thread 1 
synchronized(lock) { 
    ready = true; 
    lock.notifyAll(); 
} 


// thread 2 
synchronized(lock) { 
    while(!ready) 
     lock.wait(); 
} 

使用這種方法,線程1或線程2是否首先獲取鎖並不重要。

如果您使用通知或等待而不設置值或檢查值,某些編碼分析工具會給您一個警告。

+0

是的,就是這樣。謝謝! – Kosta 2011-12-19 14:12:56

+2

這不會導致死鎖情況:線程2獲取'lock'並且在它離開塊時纔會釋放(但它不會離開,因爲它正在等待'ready'爲真,這永遠不會發生而線程2持有'鎖定')? – Thor84no 2011-12-19 16:51:48

+3

'lock.wait()'釋放'lock'並在返回之前重新獲取它。 – 2011-12-19 18:06:33

2

最簡單的方法是隻是說

firstThread.join();

這將阻止,直到第一個線程終止。

但是你可以使用wait/notify來實現。不幸的是,你沒有發佈你的真實代碼片段,但我想如果等待不會退出,當你打電話通知它發生,因爲你沒有把這兩個​​塊。請注意,​​塊的「參數」對於等待/通知對必須相同。

+0

對不起,但在這種情況下,第一個線程無限期地在while循環中運行,所以firstThread.join()只能運行一次。 wait()不會退出,因爲它在notifyAll()之後被調用。 synchronized塊和wait()/ notifyAll()都具有相同的參數。 – Kosta 2011-12-19 13:57:00

3

您可以使用wait()超時,在這種情況下,您不會冒着永遠等待的風險。還要注意,即使根本沒有notify(),wait()也可能返回,因此,您需要在等待循環中包裝等待。這是用Java等待的標準方式。

synchronized(syncObject) { 
    while(condition.isTrue()) { 
     syncObject.wait(WAIT_TIMEOUT); 
    } 
} 

(在你的線程2)

編輯:感動外循環同步。

+0

問題是線程2錯過了第一個notifyAll(),超時並沒有幫助。但是:我錯過了我的問題中的虛假喚醒,所以我也需要解釋這一點。條件變量實際上是我需要的 - 但我更喜歡while(condition.isFalse()) – Kosta 2011-12-19 14:01:44

+0

是的,當然,您可以使用isFalse()。 while循環也可以在synchronized節中。這段代碼只是爲了展示大致的想法。 – weekens 2011-12-19 14:20:08

+0

這樣做的問題是,在檢查之後但獲取鎖之前,條件可能會變爲真。這意味着你最終無緣無故地等待()。 – 2011-12-19 18:08:05

2

我會在兩個線程之間使用BlockingQueue。使用waitnotify是如此5分鐘前;)

enum Event { 
    Event, 
    Stop; 
} 

BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>(); 

// Thread 1 
try { 
    while(true) { 
    ...do something... 
    queue.put(Event.Event); 
    ...wait for some condition that might never happen... 
    ... 
    } 
} finally { 
    // Tell other thread we've finished. 
    queue.put(Event.Stop}; 
} 

// Thread 2 
... 
switch (queue.take()) { 
    case Event: 
     ... 
     break; 

    default: 
     ... 
     break; 
} 
+0

這是有效的,但我不喜歡它的是線程1必須知道有多少線程在等待它:如果你有n個線程正在take()ing,線程1需要放置n個事件 – Kosta 2011-12-19 14:11:27

+0

@Kosta - 你能否在你的問題上擴展這個要求?我相信我們也可以提供這種功能。 – OldCurmudgeon 2011-12-19 14:14:24

+0

我想你可能也想看看CyclicBarrier - 這將處理你命名的所有工作,並且不需要任何低級代碼:) - http://docs.oracle.com/javase/6/docs/api/的java/util的/並行/ CyclicBarrier.html – 2011-12-19 15:08:10