VIBrunazo
VIBrunazo

Reputation: 1728

Why am I getting java.util.ConcurrentModificationException with an iterator?

I'm trying to write a game, so every frame I call the doDraw() method where I'm using an iterator to loop through all GameObjects and print them all on screen:

Iterator<GameObject> itr = mObjList.iterator();
    while (itr.hasNext()) {
        GameObject obj = itr.next(); // this line gives me the error
        ...
        // print object
    }

The only method that adds item to the list, is this:

public void click(int x, int y) {
    // adds new object to the list on a click event
    mObjList.add(new GameObject(x, y));
}

Most of the times it works. But sometimes I get this error:

java.util.ConcurrentModificationException

From the line with "itr.next()". From what I've googled, I figured this is because the click() event sometimes happen before the draw() finishes drawing every object, so it's changing the list while the iterator is using it. I suppose this is what's wrong?

But I'm not experienced with threads. How could I possibly fix this? Maybe I'm doing this whole thing wrong and I should use a completely different method to print all objects on screen?

Upvotes: 1

Views: 1130

Answers (5)

Gray
Gray

Reputation: 116938

I'd like add to the answer from @aleph_null. @aleph_null is correct that this exception happens when you try to modify a collection while you are iterating across it -- only the remove() method on the iterator is allowed. The iterator is trying to protect itself from changes happening to the collection underneath it.

I would not, however, recommend synchronization as the right solution. If you need the behavior of adding stuff to a list while you are processing it then I recommend adding to another list and then calling addAll() once you stop iterating. More GC intensive for sure but cleaner.

Edit:

Sorry, I missed the fact that the click() is an asynchronous event handed by another thread. I assumed that the click() was called inside the loop. You will have to synchronize around the list when you add in click() and around the addAll(). You could use an AtomicReference to record the click and then act on it after the iterator finished but only if you were guaranteed of only one item being clicked on at a time.

Upvotes: 2

zeke
zeke

Reputation: 1

Your guess about why you're getting the ConcurrentModificationException is right on.

To fix this, you could synchronize on the list itself:

synchronized(mObjList) {
    Iterator itr = mObjList.iterator();
    while (itr.hasNext()) {
        // ...
    }
}

This will allow your thread to acquire a lock on the list itself. If you wrap your access to the list from the other thread in a similar fashion, it will prevent your threads from stepping on each other (see Intrinsic Locks).

I can't tell from context if this would wreak unnecessary havoc in your other thread, but if the list is small-ish it shouldn't be problem. It will certainly cause less problems than an intermittent exception.

Upvotes: 0

Matt Ball
Matt Ball

Reputation: 360056

If the expected number of reads and traversals greatly outnumbers the number of updates to the list, use a CopyOnWriteArrayList.

Otherwise, synchronize on the list (or a dedicated mutex object) when iterating and mutating:

private List<GameObject> mObjList = /* whatever */;
private final Object mListMutex = new Object();

// snip...

synchronized (mListMutex) {
    for (GameObject obj : mObjList) {
        // do your thang
    }
}

// snip...
public void click(int x, int y) {
    GameObject obj = new GameObject(x, y);
    synchronized (mListMutex) {
        mObjList.add(obj);
    }
}

Upvotes: 4

aleph_null
aleph_null

Reputation: 5786

noooo, synchronization errors are nasty... especially since they seem to occur randomly. While traversing a collection with an iterator, you cannot modify the collection (unless you use the iterator's remove method, but that's an exception). Doing so results in the exception you're looking at... but only sometimes. The error will occur only when mObjList.add gets called right when you're traversing the iterator.

Upvotes: 0

Hot Licks
Hot Licks

Reputation: 47759

The solution is to not use an iterator, or somehow ping-pong between lists so that the one you're modifying is not the one you're iterating through. You could also try to use synchronization, but that requires some fairly sophisticated skills to do well without creating deadlocks.

Upvotes: 0

Related Questions