5

オブザーバー デザイン パターンでは、サブジェクトupdate()は各オブザーバーの操作を呼び出して、すべてのオブザーバーに通知します。これを行う1つの方法は

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

ただし、ここでの問題は、各オブザーバーが順番に更新され、オブザーバーの更新操作が、更新される前にすべてのオブザーバーまで呼び出されない可能性があることです。更新のための無限ループを持つオブザーバーがある場合、それ以降のすべてのオブザーバーには通知されません。

質問:

  1. この問題を回避する方法はありますか?
  2. もしそうなら、良い例は何ですか?
4

7 に答える 7

20

問題は、次から次への通知ではなく、無限ループです。

物事を同時に更新したい場合は、異なるスレッドで物事を開始する必要があります。その場合、イベントを開始したオブジェクトにアクセスするために、各リスナーは他のリスナーと同期する必要があります。

1 つの無限ループが他の更新の発生を妨げていると不平を言うのは、ロックを取得してから無限ループに入ると、他のユーザーがロックされたオブジェクトにアクセスできなくなると不平を言うようなものです。問題は、ロック マネージャーではなく、無限ループです。

于 2009-12-07T20:26:27.023 に答える
10

古典的な設計パターンには、並列処理とスレッド化は含まれません。N 個のオブザーバーに対して N 個のスレッドを生成する必要があります。ただし、これに対する対話はスレッドセーフな方法で行う必要があるため、注意してください。

于 2009-12-07T20:24:10.130 に答える
5

java.utils.concurrent.Executors.newFixedThreadPool(int nThreads) メソッドを使用してから、invokeAll メソッドを呼び出すことができます (無限ループを回避するために、タイムアウトのあるメソッドも使用できます)。

ループを変更して、「observer」と「this」を受け取る Callable のクラスを追加し、「call」メソッドで update メソッドを呼び出します。

詳細については、このパッケージをご覧ください

これは、私が話していたことの簡単で汚い実装です。

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");
    }
}
于 2009-12-07T20:29:05.367 に答える
3

オブザーバーが無期限にループすることよりも、例外をスローすることの方が心配です。現在の実装では、そのようなイベントで残りのオブザーバーに通知しません。

于 2009-12-07T21:44:19.743 に答える
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 回実行する必要があります)。

他のメカニズムは、2 番目のスレッドで新しいタスクを開始することですが、無限ループに入ると、最終的にすべてのシステム メモリが消費されます。

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

これにより、そのオブザーバー インスタンスがすぐに返されますが、それは幻想にすぎません。実際に行う必要があるのは、無限ループを回避することです。

最後に、オブザーバーは正常に動作しているが、すべてのオブザーバーにすぐに通知したい場合は、次の関連する質問を参照してください:すべてのマウス イベント リスナーが実行された後にコードを呼び出します。.

于 2009-12-07T21:18:00.073 に答える
0

すべてのオブザーバーが通知を受け取ります。それが保証のすべてです。

派手な順序付けを実装したい場合は、次のように実行できます。

  • Observer を 1 つだけ接続します。
  • このプライマリ オブザーバーに、コードまたはその他の方法で定義した順序で友人に通知してもらいます。

これは、リスナーがハードワイヤードであるという点で、従来の Observer パターンから離れますが、それが必要な場合は... 実行してください!

于 2009-12-07T20:31:23.473 に答える
0

「無限ループ」を持つオブザーバーがある場合、それはもはやオブザーバー パターンではありません。

オブザーバーごとに異なるスレッドを起動できますが、オブザーバーは監視対象オブジェクトの状態を変更することを禁止する必要があります。

最も単純な(そして最も愚かな)方法は、単にあなたの例をスレッド化することです。

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

(これは手作業でコーディングされており、テストされておらず、おそらく 1 つまたは 5 つのバグがあります。とにかく、それは悪い考えです)

これに関する問題は、一度に多数の新しいスレッドを割り当てる必要があるため、マシンが分厚くなってしまうことです。

したがって、すべてのスレッドが同時に開始される問題を解決するには、ThreadPoolExecutor を使用します。これは、A) スレッドをリサイクルし、B) 実行中のスレッドの最大数を制限できるためです。

各永久ループはプールからスレッドの 1 つを永久に消費するため、「永遠にループ」の場合、これは決定論的ではありません。

あなたの最善の策は、それらが永遠にループすることを許可しないか、必要に応じて独自のスレッドを作成することです。

変更できないクラスをサポートする必要があるが、どれがすばやく実行され、どれが「永遠に」実行されるかを識別できる場合 (コンピューター用語では、1 秒または 2 秒以上に相当すると思います)、次のようなループを使用できます。これ:

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

ねえ、実際に「永遠にループする」場合、通知ごとにスレッドを消費しますか? デザインにもう少し時間を費やす必要があるようです。

于 2009-12-07T21:03:03.947 に答える