2010-12-15 146 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,以便它們不會交錯。這當然意味着觀察者會在同步塊內部得到通知,這違反了作者的聲明,認爲這不應該發生。因此,很難推薦使用這個類。