在观察者设计模式中,主体通过调用每个观察者的update()
操作来通知所有观察者。一种方法是
void notify() {
for (observer: observers) {
observer.update(this);
}
}
但是这里的问题是每个观察者都按顺序更新,观察者的更新操作可能不会被调用,直到更新之前的所有观察者。如果有一个观察者有一个无限循环进行更新,那么它之后的所有观察者将永远不会得到通知。
问题:
答案 0 :(得分:20)
问题是无限循环,而不是一个接一个的通知。
如果你想要同时更新内容,你需要在不同的线程上解决问题 - 在这种情况下,每个监听器都需要与其他监听器同步才能访问触发事件的对象。
抱怨一个无限循环阻止其他更新发生就像抱怨拿一个锁然后进入无限循环阻止其他人访问锁定对象 - 问题是无限循环,而不是锁管理器。
答案 1 :(得分:10)
经典设计模式不涉及并行和线程。您必须为N个观察者生成N个线程。但要小心,因为他们与这个的互动必须以线程安全的方式完成。
答案 2 :(得分:5)
你可以使用java.utils.concurrent.Executors.newFixedThreadPool(int nThreads)方法,然后调用invokeAll方法(也可以使用带有timout的方法来避免无限循环)。
你会改变你的循环来添加一个Callable类,它接受“observer”和“this”,然后在“call”方法中调用update方法。
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");
}
}
答案 3 :(得分:3)
我更关心的是观察者抛出异常而不是无限循环。您当前的实施不会在这种情况下通知剩余的观察员。
答案 4 :(得分: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.。
答案 5 :(得分:0)
所有观察者都会得到通知,这就是你得到的所有保证。
如果你想实现一些花哨的订购,你可以这样做:
这会让你远离经典的观察者模式,因为你的听众是硬连线的,但如果它是你需要的......做吧!
答案 6 :(得分: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();
}
}
嘿,如果它实际上是“永远循环”,它会为每个通知消耗一个线程吗?听起来你可能不得不花更多的时间在你的设计上。