2009-12-07 243 views
4

Observer設計模式,主體通過調用每一個觀察者的update()操作,通知所有的觀察者。這樣做的一種方式是Observer設計模式

void notify() { 
    for (observer: observers) { 
     observer.update(this); 
    } 
} 

但這裏的問題是,每個觀察者序列,並且更新操作更新的觀察者可能不會被稱爲直到所有的觀察家它更新之前。如果有一個觀察者有一個無限循環用於更新,那麼它之後的所有觀察者將永遠不會被通知。

問:

  1. 有沒有辦法來解決這個問題呢?
  2. 如果是這樣會是一個很好的例子嗎?

回答

10

經典的設計模式,不涉及並行線程和。你必須爲N個觀察者產生N個線程。要小心,因爲他們的互動將必須以線程安全的方式完成。

20

問題是無限循環,而不是一個後 - 其他通知。

如果你想要的東西同時更新,你需要火的東西掉在不同的線程 - 在這種情況下,每個聽衆都需要爲了訪問觸發事件的對象與他人同步。

抱怨約一個無限循環停止發生其他更新就像抱怨說,採取一個鎖,然後進入無限循環阻止他人訪問鎖定的對象 - 問題是無限循環,而不是鎖管理器。

+0

+1爲解決問題而不是症狀。否則,你會越來越瘋狂的附加修補程序,使維護變得不可能。 – reccles 2009-12-07 21:20:06

+0

我同意。我只是想看看是否有辦法解決這個問題。謝謝。 – suprasad 2009-12-08 01:07:00

5

您可以使用java.utils.concurrent.Executors.newFixedThreadPool(int nThreads)方法,然後調用invokeAll方法(可以利用timout中的方法來避免無限循環)。

你會改變你的循環添加一個類,它是可調用的,是以「觀察員」和「本」,然後調用更新方法在「通話」的方法。

Take a look at this package for more info

這是一個快速和骯髒的執行了我在談論的:

import java.util.ArrayList; 
import java.util.List; 
import java.util.concurrent.Callable; 
import java.util.concurrent.CopyOnWriteArrayList; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.TimeUnit; 

public class Main 
{ 
    private Main() 
    { 
    } 

    public static void main(final String[] argv) 
    { 
     final Watched  watched; 
     final List<Watcher> watchers; 

     watched = new Watched(); 
     watchers = makeWatchers(watched, 10); 
     watched.notifyWatchers(9); 
    } 

    private static List<Watcher> makeWatchers(final Watched watched, 
               final int  count) 
    { 
     final List<Watcher> watchers; 

     watchers = new ArrayList<Watcher>(count); 

     for(int i = 0; i < count; i++) 
     { 
      final Watcher watcher; 

      watcher = new Watcher(i + 1); 
      watched.addWatcher(watcher); 
      watchers.add(watcher); 
     } 

     return (watchers); 
    } 
} 

class Watched 
{ 
    private final List<Watcher> watchers; 

    { 
     watchers = new ArrayList<Watcher>(); 
    } 

    public void addWatcher(final Watcher watcher) 
    { 
     watchers.add(watcher); 
    } 

    public void notifyWatchers(final int seconds) 
    { 
     final List<Watcher>   currentWatchers; 
     final List<WatcherCallable> callables; 
     final ExecutorService  service; 

     currentWatchers = new CopyOnWriteArrayList<Watcher>(watchers); 
     callables  = new ArrayList<WatcherCallable>(currentWatchers.size()); 

     for(final Watcher watcher : currentWatchers) 
     { 
      final WatcherCallable callable; 

      callable = new WatcherCallable(watcher); 
      callables.add(callable); 
     } 

     service = Executors.newFixedThreadPool(callables.size()); 

     try 
     { 
      final boolean value; 

      service.invokeAll(callables, seconds, TimeUnit.SECONDS); 
      value = service.awaitTermination(seconds, TimeUnit.SECONDS); 
      System.out.println("done: " + value); 
     } 
     catch (InterruptedException ex) 
     { 
     } 

     service.shutdown(); 
     System.out.println("leaving"); 
    } 

    private class WatcherCallable 
     implements Callable<Void> 
    { 
     private final Watcher watcher; 

     WatcherCallable(final Watcher w) 
     { 
      watcher = w; 
     } 

     public Void call() 
     { 
      watcher.update(Watched.this); 
      return (null); 
     } 
    } 
} 

class Watcher 
{ 
    private final int value; 

    Watcher(final int val) 
    { 
     value = val; 
    } 

    public void update(final Watched watched) 
    { 
     try 
     { 
      Thread.sleep(value * 1000); 
     } 
     catch (InterruptedException ex) 
     { 
      System.out.println(value + "interupted"); 
     } 

     System.out.println(value + " done"); 
    } 
} 
0

所有觀察員獲得通知,這就是你得到了保證。

如果你想實現一些奇特的排序,你可以這樣做:

  • 連接只是一個單一的觀察員;
  • 有這個主要觀察者以您在代碼或其他方式定義的順序通知他的朋友。

這會讓你遠離經典的觀察者模式,因爲你的聽衆是硬連線的,但如果這是你需要的......做到這一點!

0

如果你有一個「無限循環」的觀察員,它不再是真正的觀察者模式。

你可以發射不同的線程每個觀察者,但觀察者必須從觀察對象上改變國家被禁止。

最簡單的(和愚蠢的)方法,簡直是把你的榜樣,使之穿過。

void notify() { 
    for (observer: observers) { 
     new Thread(){ 
      public static void run() { 
       observer.update(this); 
      } 
     }.start(); 
    } 
} 

(這是由手工編碼,是未經檢驗的,可能有一個bug,五 - 這是一個壞主意,反正)

這裏的問題是,它會讓你的機器笨重的,因爲它必須一次分配一堆新線程。

所以修復起步價一旦所有的踏板問題,使用ThreadPoolExecutor的,因爲它將A)回收線程,B)可以限制運行的線程的最大數量。

這是不是在你的情況下,「死循環」的確定性,因爲每個永遠循環將永久吃從池中一個線程。

最好的辦法是不要讓他們永遠循環下去,或者,如果有必要,讓他們創建自己的線程。

如果你不得不支持那些不能改變的類,但你可以確定哪些將會很快運行,哪些運行「永遠」(用計算機術語來說,我認爲這相當於超過一秒或兩秒),那麼你可能會使用這樣一個循環:

void notify() { 
    for (observer: observers) { 
     if(willUpdateQuickly(observer)) 
      observer.update(this); 
     else 
      new Thread(){ 
       public static void run() { 
        observer.update(this); 
       } 
      }.start(); 
    } 
} 

嘿,如果它實際上「永遠循環」,它會消耗一個線程爲每個通知?這聽起來好像你可能需要花更多的時間在你的設計上。

2

1.有辦法解決這個問題嗎?

是的,確保觀察者工作正常,並及時返回。

2.有人可以請用一個例子來解釋一下。

肯定的:

class ObserverImpl implements Observer { 
    public void update(Object state) { 
      // remove the infinite loop. 
      //while(true) { 
      // doSomething(); 
      //} 

      // and use some kind of control: 
      int iterationControl = 100; 
      int currentIteration = 0; 
      while(curentIteration++ < iterationControl) { 
       doSomething(); 
      } 
    } 
    private void doSomething(){} 
} 

這一個從給定循環防止去無限的(如果這是有道理的,它應該爲100次運行)

其他機制啓動新任務在第二個線程中,但是如果進入無限循環,它將最終消耗所有系統內存:

class ObserverImpl implements Observer { 
    public void update(Object state) { 
     new Thread(new Runnable(){ 
      public void run() { 
       while(true) { 
        doSomething(); 
       } 
      } 
      }).start(); 
    } 
    private void doSomething(){} 
} 

這將使觀察者實例立即返回,但這只是一種幻覺,你必須實際做的是避免無限循環。

最後,如果你的觀察員工作正常,但你只是想早日通知他們,你可以看看這個相關的問題:Invoke a code after all mouse event listeners are executed.

3

我會更關心拋出異常的觀察者而不是無限循環。您目前的實施將不會在此類事件中通知餘下的觀察員。