I am writing a GUI application using the Immediate Mode GUI pattern, and the UI runs on a thread separate to the engine that powers the actual functionality of the application. The GUI thread ends up iterating over many lists of objects that are conceptually "owned" by the engine thread, and these lists change extremely infrequently. The GUI thread is vsync'ed, meaning it runs at about 60Hz, and the engine thread runs at about 200Hz.
Sometimes, actions in the UI will change the contents of the collections in the engine, and I have a message-passing system to post Runnables to the engine thread to do these mutations to ensure that these mutations don't collide with what's happening in the engine. That way, I can ensure that the engine always sees a consistent view of the data, which for my application is very important.
Because the engine is in charge of all data mutations, though, it sometimes happens that the engine changes the contents of a collection while the GUI is iterating through it, and because these collections are standard Java collections, this predictably and correctly throws a ConcurrentModificationException
. I can think of a few high-level ways to deal with this:
Locking comes with a significant performance penalty, and while it would be fine for the GUI to sometimes stall while waiting to acquire the lock from the engine thread, it is very important for the engine thread to run at a consistent speed, and even an R/W lock would cause stalls in the engine thread. Double-buffering comes with significant complexity, as there is a lot of data that is read by the GUI on each frame.
I give you all of this background because I know that option 3 is ugly, and that my question is in some sense "not the right question". The Javadoc for ConcurrentModificationException
even says:
Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.
But! I am not concerned with the correctness of the GUI for the single frame that would be marred by the CME. I am only concerned with what happens on the next frame. Which leads to my question: is it safe to continue using a Java collection (I'm most interested in the answer for ArrayList
and HashMap
) after a ConcurrentModificationException
has been thrown from its iterator? It seems logical that it would be, but I can't find a piece of documentation that says that the object will still be in a usable state after the CME is thrown. Obviously the iterator is toast at that point, but I'd like to swallow the exception and continue using the collection.
Quoting part of the quote:
Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.
Conclusion: Your code is buggy and needs to be fixed, so it doesn't matter what state the collection is in.
You should never get that error in valid code. It's an exception that should never be caught and acted on.
For most collections, as long as you only have one writer, you are probably ok, but there are no guarantees. If you have multiple writers on a HashMap you can end up with a corrupt map which has an infinite loop in it's structure.
I suggest you use ReentrantLock
which has a tryLock()
method if you want to support only-obtaining-a-lock-if-it's-not-being-used approach to minimise blocking.
Another way to feed data from one thread to another is to Queue
modification tasks which the engine can pick up when not otherwise busy. This allows all access through one thread only.
BTW: A lock/unlock takes around 1 micro-second which is unlikely to make much difference to you unless you do this a lot.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With