2012-01-03 75 views
1

此方法通知事件循環開始處理消息。但是,如果事件循環已經在處理消息,則此方法將阻塞,直到它收到完成事件處理的通知(在事件循環結束時觸發)。服務器模式下的線程塊

public void processEvent(EventMessage request) throws Exception { 
    System.out.println("processEvent"); 

    if (processingEvent) { 
     synchronized (eventCompleted) { 
      System.out.println("processEvent: Wait for Event to completed"); 
      eventCompleted.wait(); 
      System.out.println("processEvent: Event completed"); 
     } 
    } 

    myRequest = request; 
    processingEvent = true; 
    synchronized (eventReady) { 
     eventReady.notifyAll(); 
    } 
} 

這適用於客戶端模式。如果我切換到服務器模式,並且在事件循環中處理消息的時間太快,那麼上面的方法會永遠等待事件完成。出於某種原因,事件完成通知在processingEvent檢查之後和eventCompleted.wait()之前發送。如果我刪除輸出語句,這沒有區別。我無法在客戶端模式下重複相同的問題。

爲什麼這隻會發生在服務器模式,我該怎麼做才能防止這種情況發生?

這裏是eventReady等待和eventCompleted通知:

public void run() { 
    try { 
     while (true) { 
      try { 
       synchronized (eventReady) { 
        eventReady.wait(); 
       } 
       nx.processEvent(myRequest, myResultSet); 
       if (processingEvent > 0) { 
        notifyInterface.notifyEventComplete(myRequest); 
       } 
      } catch (InterruptedException e) { 
       throw e; 
      } catch (Exception e) { 
       notifyInterface.notifyException(e, myRequest); 
      } finally { 
       processingEvent--; 
       synchronized (eventCompleted) { 
        eventCompleted.notifyAll(); 
       } 
      } 
     } // End of while loop 
    } catch (InterruptedException Ignore) { 
    } finally { 
     me = null; 
    } 

下面是修改後的代碼,這似乎沒有死鎖問題的工作 - 這BTW發生在randomely客戶端模式後約300事件。

private BlockingQueue<EventMessage> queue = new SynchronousQueue<EventMessage>(); 

public void processEvent(EventMessage request) throws Exception { 
    System.out.println("processEvent"); 

    queue.put(request); 
} 


public void run() { 
    try { 
     while (true) { 
      EventMessage request = null; 
      try { 
       request = queue.take(); 
       processingEvent = true; 
       nx.processEvent(request, myResultSet); 
       notifyInterface.notifyEventComplete(request); 
      } catch (InterruptedException e) { 
       throw e; 
      } catch (Exception e) { 
       notifyInterface.notifyException(e, request); 
      } finally { 
       if (processingEvent) { 
        synchronized (eventCompleted) { 
         processingEvent = false; 
         eventCompleted.notifyAll(); 
        } 
       } 
      } 
     } // End of while loop 
    } catch (InterruptedException Ignore) { 
    } finally { 
     me = null; 
    } 
} 

回答

0

你的代碼中有很多競態條件。即使聲明processingEventvolatile或使用AtomicBoolean也無濟於事。我建議使用SynchronousQueue這將阻止事件,直到處理器準備就緒。喜歡的東西:

private final BlockingQueue<Request> queue = new SynchronousQueue<Request>(); 
... 

// this will block until the processor dequeues it 
queue.put(request); 

然後事件處理器的作用:

while (!done) { 
    // this will block until an event is put-ed to the queue 
    Request request = queue.take(); 
    process the event ... 
} 

只有一個請求將立即與所有的同步處理等將由SynchronousQueue處理。

+0

我在看到您的評論之前實施了您的方法。它現在很好用! – mtse 2012-01-03 22:07:55

+0

我相信你的意思是LinkedBlockingQueue的限制是1,而不是0.根據API文檔,0的限制會在構造函數中拋出一個IllegalArgumentException。 – 2012-01-03 22:31:01

+0

第1號將使其排隊1事件。 1個工作和1個排隊。 0意味着提供者將阻塞,直到它由事件處理器出列。沒有事件排隊。 0是典型的模式。 – Gray 2012-01-03 22:32:44

0

如果processingEvent未聲明volatile或從​​塊內的訪問,然後由一個線程所做的更新可能不會立即對其他線程可見。但是,從代碼中不清楚這是否是這種情況。

「服務器」虛擬機針對速度進行了優化(以啓動時間和內存使用率爲代價),這可能是使用「客戶端」虛擬機時未遇到此問題的原因。

1

如果您調用notifyAll並且沒有線程在等待(),則通知將丟失。

正確的方法是在調用notify()時始終更改同步塊內部的狀態,並在調用wait()之前始終在同步塊內檢查該狀態。

此外,您對processingEvent的使用似乎不是線程安全的。

您能否提供等待eventReady的代碼並通知eventCompleted

如果您的應用程序加速或放慢速度恰到好處,您的程序可能會發生作用。如果你使用-client,但是如果你使用不同的機器,JVM或JVM選項,它可能會失敗。

+0

你提到檢查狀態調用wait()之前,但問題是,總是會有檢查狀態和調用wait之間的間隙,所以你需要一些類型的同步以防止競爭條件(不管狀態如何/何時更新)。 – 2012-01-03 20:00:48

+0

@ increment1謝謝。已添加澄清。 – 2012-01-03 20:13:37

+0

我認爲描述中添加的同步塊可能會導致死鎖問題,因爲如果您的意思是在同一個鎖上進行同步,那麼如果等待線程持有該鎖,則釋放線程將永遠無法獲取該鎖並觸發通知。 – 2012-01-03 20:45:18

0

代碼中存在競爭條件,可能會因使用服務器虛擬機而感到憤怒,並且如果processingEvent不是易失性的,那麼服務器虛擬機或其環境所做的某些優化可能會進一步影響問題。

你的代碼問題(假設這個方法被多個線程同時訪問)是在你的processEvent和eventCompleted.wait()檢查之間,另一個線程已經可以通知和(我假設)將processingEvent設置爲false。

阻塞問題的最簡單的解決方案是不要試圖自己管理它,而只是讓JVM通過使用共享鎖來完成它(如果你只想一次處理一個事件)。所以你可以只是同步整個方法,而不用擔心。

第二個簡單的解決方案是爲事件傳遞使用SynchronousQueue(這是它設計的情況類型);或者如果您有更多的執行線程並且一次需要隊列中有多個元素,那麼您可以改用ArrayBlockingQueue。例如:

private SynchronousQueue<EventMessage> queue = new SynchronousQueue<EventMessage>(); 

public void addEvent(EventMessage request) throws Exception 
{ 
    System.out.println("Adding event"); 

    queue.put(request); 
} 

public void processNextEvent() 
{ 
    EventMessage request = queue.take(); 
    processMyEvent(request); 
} 

// Your queue executing thread 
public void run() 
{ 
    while(!terminated) 
    { 
    processNextEvent(); 
    } 
} 
+0

我使用LinkedBlockingQueue,因爲我需要FIFO,並且還能夠控制將調用線程阻塞到processEvent的時間長度。 – mtse 2012-01-03 22:17:57

+0

@mtse SynchronousQueue沒有大小,並且基本上只有一個元素(它具有與大小爲1的LinkedBlockingQueue相同的功能,只是我認爲性能會更好)。所有隊列都具有FIFO功能,所有阻塞隊列都可以通過報價和輪詢在有限的時間內阻塞。如果你喜歡格雷的答案,那麼你應該注意它(向上箭頭)並接受它(複選標記)。 – 2012-01-03 22:24:42

+0

我會投票,但我沒有足夠的聲譽 - 這是我在Stackoverflow的第一個問題! – mtse 2012-01-03 22:30:52