Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a Java collection guaranteed to be in a valid, usable state after a ConcurrentModificationException?

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:

  1. locking, either by using a synchronized collection or read-write locks
  2. double-buffer the data that the GUI thread reads, and have the GUI thread flip the double-buffer when it's done drawing a frame
  3. ignore the CME and abort drawing the rest of the frame, which will draw partial information for the frame in which the "bad" mutation happens, and just continue on to the next frame

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.

like image 468
Haldean Brown Avatar asked Mar 21 '19 18:03

Haldean Brown


2 Answers

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.

like image 168
Andreas Avatar answered Sep 29 '22 15:09

Andreas


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.

like image 23
Peter Lawrey Avatar answered Sep 29 '22 16:09

Peter Lawrey