update()
操作来通知所有观察者。其中一种方法是:void notify() {
for (observer: observers) {
observer.update(this);
}
}
然而,这里的问题是每个观察者都按顺序更新,并且在它之前的所有观察者被更新之前,可能不会调用观察者的更新操作。如果存在一个观察者更新时出现无限循环,则其后的所有观察者将永远不会收到通知。
问题:
- 有没有办法解决这个问题?
- 如果有,可以举个好的例子吗?
update()
操作来通知所有观察者。其中一种方法是:void notify() {
for (observer: observers) {
observer.update(this);
}
}
然而,这里的问题是每个观察者都按顺序更新,并且在它之前的所有观察者被更新之前,可能不会调用观察者的更新操作。如果存在一个观察者更新时出现无限循环,则其后的所有观察者将永远不会收到通知。
问题:
问题在于无限循环,而不是一个接一个的通知。
如果你希望同时更新事物,你需要在不同的线程上启动它们——在这种情况下,每个监听器都需要与其他监听器同步,以便访问触发事件的对象。
抱怨一个无限循环阻止其他更新发生就像抱怨获取锁之后进入无限循环会阻止其他人访问被锁定的对象——问题在于无限循环,而不是锁管理器。
经典的设计模式不涉及并行性和线程。您需要为N个观察者生成N个线程。但要小心,因为它们与this的交互必须以线程安全的方式完成。
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");
}
}
我更关注观察者抛出异常的情况,而不是它无限循环的情况。在这种情况下,您当前的实现不会通知剩余的观察者。
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(){}
}
class ObserverImpl implements Observer {
public void update( Object state ) {
new Thread( new Runnable(){
public void run() {
while( true ) {
doSomething();
}
}
}).start();
}
private void doSomething(){}
}
如果您的观察者存在“无限循环”,那么它实际上已经不再是观察者模式。
您可以为每个观察者启动一个不同的线程,但必须禁止观察者更改被观察对象的状态。
最简单(也是最愚蠢)的方法就是将您的示例转换为多线程。
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();
}
}
嘿,如果它实际上“永远循环”,那么每个通知都会消耗一个线程吗?听起来你可能需要花更多时间在设计上。
所有观察者都会收到通知,这就是你所能得到的保证。
如果你想要实现一些花哨的排序,你可以这样做:
这使你远离了经典的观察者模式,因为你的监听器是硬编码的,但如果这正是你需要的...那就去做吧!