2013年12月31日

ConcurrentModificationException in java

我們一個專案曾經在某些特殊的狀況下,出現了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物件。