列表的遍曆有兩種方式,一種是採用for迴圈,如下所示:
List list=new ArrayList();
for(int i=0;i<list.size();i++)...{
//...
}
還有一種是採用Iterator介面 ,如下所示:
Iterator ite=list.iterator();
while(ite.hasNext())...{//...
}
那麼這兩種方式有什麼差別呢?答案是在順序執行的程式中,他們沒有差別;但是在並行程式中有差別。如果在遍曆的過程中有另外一個線程修改了list,那麼採用for迴圈其執行結果是不確定的,而採用Iterator會快速失敗(fast-fail)並拋出ConcurrentModificationException。所以在通常情況下,優先使用Iterator遍曆集合對象。
有時候我們需要在遍曆過程中保證集合不被修改,這就需要對集合進行同步。
synchronized(list)...{
Iterator ite=list.iterator();
while(ite.hasNext())...{
//...
}
}
上面這種方式需要在遍曆過程中完全鎖住被訪問的list對象,如果遍曆過程耗時較長容易導致效能下降和死結。所以有時可以採用臨時拷貝的方法,在拷貝後的私人臨時集合上執行遍曆操作。
Object[] snapshot;
synchronized (list) ...{
snapshot = new Object[list.size()];
for (int i = 0; i < snapshot.length; ++i)
snapshot[i] = list.get(i);
}
for (int i = 0; i < snapshot.length; ++i) ...{
//...
}
下面考慮一個實際的例子,就是observer模式。一個目標對象有多個觀察者,當目標狀態發生變化時,它向各個觀察者發出通知。下面是tomcat5中ContainerBase類的一個方法:
public void fireContainerEvent(String type, Object data) ...{
if (listeners.size() < 1)
return;
ContainerEvent event = new ContainerEvent(this, type, data);
ContainerListener list[] = new ContainerListener[0];
synchronized (listeners) ...{
list = (ContainerListener[]) listeners.toArray(list);
}
for (int i = 0; i < list.length; i++)
((ContainerListener) list[i]).containerEvent(event);
}
可見它採用的是臨時拷貝的方法。
下面是spring中ApplicationEventMulticasterImpl類的一個方法:
public void onApplicationEvent(ApplicationEvent e) ...{
Iterator i = eventListeners.iterator();
while (i.hasNext()) ...{
ApplicationListener l = (ApplicationListener) i.next();
l.onApplicationEvent(e);
}
}
可見spring採用的是未同步的Iterator方法。
我認為Spring的寫法是不恰當的。這涉及到伺服器端應用和用戶端應用異常處理方式的問題。對於用戶端應用,完全可以採用不同步的Iterator方式。當異常發生時提示操作者是放棄操作,還是重試。但是對於伺服器端應用,必須保證在遍曆的過程中不發生異常。Spring作為一個通用的組件管理架構,當然不能假定其使用的場合。
後記:java5中的java.util.concurrent包中的集合類,提供了另外一種形式的weakly-consistent iterator。它不同於傳統的fast-fail Iterator。they promise to be thread safe, but don't promise whether you will see any updates made to the collection since the iterator was constructed (e.g., you might see an added element, or you might not).