5

观察者设计模式中,主体通过调用每个观察者的操作来通知所有update()观察者。一种方法是

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

但是这里的问题是每个观察者都按顺序更新,并且在更新之前可能不会调用观察者的更新操作。如果有一个观察者有一个无限循环的更新,那么它之后的所有观察者将永远不会被通知。

问题:

  1. 有没有办法解决这个问题?
  2. 如果是这样,什么是一个很好的例子?
4

7 回答 7

20

问题在于无限循环,而不是一个接一个的通知。

如果您希望事情同时更新,您需要在不同的线程上触发事情 - 在这种情况下,每个侦听器都需要与其他侦听器同步才能访问触发事件的对象。

抱怨一个无限循环阻止其他更新发生就像抱怨一个锁然后进入一个无限循环阻止其他人访问锁定的对象 - 问题是无限循环,而不是锁管理器。

于 2009-12-07T20:26:27.023 回答
10

经典设计模式不涉及并行性和线程。您必须为 N 个观察者生成 N 个线程。不过要小心,因为它们与的交互必须以线程安全的方式完成。

于 2009-12-07T20:24:10.130 回答
5

您可以使用 java.utils.concurrent.Executors.newFixedThreadPool(int nThreads) 方法,然后调用 invokeAll 方法(也可以使用带有 timout 的方法以避免无限循环)。

您将更改循环以添加一个 Callable 类,该类采用“observer”和“this”,然后在“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 次)

其他机制是在第二个线程中启动新任务,但如果它进入无限循环,最终会消耗所有系统内存:

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

所有观察者都会收到通知,这就是您获得的所有保证。

如果你想实现一些花哨的排序,你可以这样做:

  • 只连接一个观察者;
  • 让这个主要的观察者按照您在代码中定义的顺序或通过其他方式通知他的朋友。

这让你远离了经典的观察者模式,因为你的听众是硬连线的,但如果这是你需要的……那就去做吧!

于 2009-12-07T20:31:23.473 回答
0

如果你有一个带有“无限循环”的观察者,它就不再是真正的观察者模式了。

您可以为每个观察者触发不同的线程,但必须禁止观察者更改被观察对象的状态。

最简单(也是最愚蠢)的方法就是以您的示例并使其线程化。

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

(这是手工编码的,未经测试,可能有一个或五个错误——无论如何这都是个坏主意)

这样做的问题是它会使你的机器变得笨重,因为它必须一次分配一堆新线程。

因此,要解决所有线程同时启动的问题,请使用 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();
   }
}

嘿,如果它真的“永远循环”,它会为每个通知消耗一个线程吗?听起来您可能需要在设计上花费更多时间。

于 2009-12-07T21:03:03.947 回答