我們一個專案曾經在某些特殊的狀況下,出現了ConcurrentModificationException,經過搜尋後得知這個錯誤會在下列狀況下發生。
當一個collection物件(包含Map.values())在執行Iterator.next()的同時如果有另一個執行緒對這個collection做add或remove item時會丟出ConcurrentModificationException。
舉例來說:在JVM裡放一個collection物件來存要開會的人。(假設有5個人的物件在collection裡),系統在會議時間開始時使用iterator來對這個collection裡的人逐一取電話來做外撥電話的動作,假設在這外撥電話的過程中(iterator尚未跑完)有另外一個執行緒add了一個開會的人到collection裡,此時iterator.next()就會被中斷且丟出錯誤。
為了避免系統會出現此錯誤我們針對了可能會有多個執行緒同時去add, remove, iterator的collection採取了下列的方式。
使用synchronized來同步Collection物件,並將iterator的邏輯封裝在裡面
public class Meeting{
private Collection<Attendee> attendee;
public void addAttendee(Attendee att){
synchronized (attendee) {
attendee.add(att);
}
}
public int dialOutAll(){
synchronized(attendee){
Iterator<Attendee> iter = attendee.iterator();
while(iter.hasNext()){
Attendee att = iter.next();
//do call out here….
}
return sum;
}
}
}
如果邏輯太過複雜不適合將iterator封裝在java bean 裡的話則使用複製的方式將collection複製出來
public class Meeting{
public List<Attendee> getAttendeeList(){
List<Attendee> list = new ArrayList<Attendee>();
synchronized(attendee){
Iterator<Attendee> iter = attendee.iterator();
while(iter.hasNext()){
list.add(iter.next());
}
}
return list;
}
}
List list = meeting.getAttendeeList();
Iterator<Attendee> iter = attendee.iterator();
while(iter.hasNext()){
Attendee attendee = iter.next();
//do something here…
}
使用ConcurrentHashMap來取代Map
ConcurrentHashMap<Key, Value> map = new ConcurrentHashMap<Key, Value>();
註:下列狀況不需考慮多執行緒問題,因為collection的scope在method裡,不會有別的執行緒去存取到
private void test(){
List<User> list = new ArryList<User>();
list.add(new User());
list.add(new User());
Iterator<User> iter = list.iterator();
while(iter.hasNext()){
User user = iter.next();
//do something here…
}
}
結論
程式碼如果有使用到iterator時就需考慮到是否會有多執行緒的問題,尤其是singleton裡的collection物件。