2010-12-15 100 views
2

在Observable的notifyObservers方法中,为什么编码器使用arrLocal = obs.toArray();? 为什么编码器不能直接迭代矢量?由于为什么可观察快照观察者矢量

public void notifyObservers(Object arg) { 

    Object[] arrLocal; 

    synchronized (this) { 
     /* We don't want the Observer doing callbacks into 
     * arbitrary code while holding its own Monitor. 
     * The code where we extract each Observable from 
     * the Vector and store the state of the Observer 
     * needs synchronization, but notifying observers 
     * does not (should not). The worst result of any 
     * potential race-condition here is that: 
     * 1) a newly-added Observer will miss a 
     * notification in progress 
     * 2) a recently unregistered Observer will be 
     * wrongly notified when it doesn't care 
     */ 
     if (!changed) 
      return; 
     arrLocal = obs.toArray(); 
     clearChanged(); 
    } 

    for (int i = arrLocal.length-1; i>=0; i--) 
     ((Observer)arrLocal[i]).update(this, arg); 
} 

回答

2

他们想避免并发修改,但同时不留太久(尤其是当他们不知道他们实际上是调用什么代码进入)同步块。

选项一将是同步整个操作,并直接遍历矢量,同时通知观察员。正如评论指出的那样(“我们不希望观察者在持有它自己的监视器时回调到任意代码中”),这将使观察者可能长时间被锁定。

选项二的同步时间足够长,以获得矢量的一致副本。 然后他们可以在迭代他们的私人副本之前释放锁。

更新:如果观察者更新观察者列表,同时迭代它可能不是一个好主意。所以即使在单线程场景中也可以看到一个副本。

+0

更糟糕的是,如果Observer尝试在其回调中添加另一个侦听器(或将其本身作为侦听器),则选项1将创建死锁。 – sje397 2010-12-15 04:42:09

+1

我认为线程本身不能死锁(观察者在同一个线程上调用)。当然,如果有更多的线程以某种方式参与进来,那么是的,那可能会发生。 – Thilo 2010-12-15 04:44:16

+0

但是,选项一会导致ConcurrentModificationException,但如果观察者试图添加另一个侦听器。所以无论如何都要建议副本。 – Thilo 2010-12-15 04:49:19

0

编码器使用“obs.toArray()”来“拍照”当前观察者。他们试图阻止对可能在其下面改变的向量进行迭代,而没有明确地同步Vector。

1

该主题中的其他答案对于副本的目的是正确的。不过,我会说,Java 5+已经有了正确的数据结构,可以自动复制:java.util.concurrent.CopyOnWriteArrayList

+0

除了CopyOnWriteArrayList对于频繁更新而言相当繁重。我认为不太经常增加观察者,所以这可能无关紧要。 – Thilo 2010-12-15 04:45:41

+0

@Thilo:事实上,一个监听器列表是'CopyOnWriteArrayList'的常用用例。 – 2010-12-15 04:46:48

+0

在这种情况下+1 ... – Thilo 2010-12-15 04:47:48

0

该代码实现了语义,notifyObservers应该准确地通知在通话时(或至少在复制时)注册的观察者。

如果不需要这种语义,那么只要迭代是线程安全的,作者就可以简单地迭代调用update的观察者。事实上,我就是这样写的。

关于这堂课是如何写作的,它是打破了我。看似,通知被要求写这样的代码:

observable.setChanged(); 
observable.notifyObservers(obj1); 

也就是说,通知第一标志着观察为改变或者notifyObservers什么都不做。另请注意,拨打notifyObservers的电话将拨打clearChanged作为其操作的一部分。

因此考虑两个线程正在进行通知的情况。这两个命令可以跨线程交错,象这样:

Thread 1: observable.setChanged(); 
Thread 2: observable.setChanged(); 
Thread 1: observable.notifyObservers(obj1); 
Thread 2: observable.notifyObservers(obj2); 

在这种情况下,预计线程1件作品notifyObservers第一个电话。但线程2第二次调用notifyObservers时什么都不做,因为第一次调用清除了更改的标志。所以观察者永远不会看到obj2的论点。

我可以看到为避免这种情况的唯一解决方案是将呼叫同步到setChangednotifyObservers,以便它们不会交错。这当然意味着观察者会在同步块内部得到通知,这违反了作者的声明,认为这不应该发生。因此,很难推荐使用这个类。