I will be much obliged if someone is able to assist me with answers to a few questions on the code below. Before I pose the questions, I provide the following notes about the code:
(1.) The code spawns a couple of threads (in addition to the thread spawned to execute the main method) and each thread processes/accesses nonconcurrent collections (i.e., an ArrayList and a HashMap).
(2.) The main method’s thread and each of the other two threads modify the collections while iterating over them using an enhanced for loop.
(3.) When the three threads mentioned in point number 2 above modify a nonconcurrent collection in an enhanced for loop, the ConcurrentModificationException gets either thrown or not, as follows:
(A.) The exception gets thrown when the main method’s thread modifies the nonconcurrent collection in the loop.
(B.) The exception does not get thrown when both of the other two threads together modify the nonconcurrent collection in the loop.
(C.) The exception does not get thrown when either of the other two threads alone modifies the nonconcurrent collection in the loop.
(4.) Depending on what lines in the code get commented out, the program execution may or may not throw the ConcurrentModificationException.
(5.) Certain lines of the program will need to be commented out, in order to check out various behaviors that I inquire about in the questions that I ask about the code.
(A.) For example, in order to check out the program behavior when only one thread is spawned (in addition to the main method’s thread), either line 32 or 33 needs to be commented out.
(B.) The lines that are currently commented out in the code are those that potentially cause the throwing of the ConcurrentModificationException.
The following are the questions I have:
(1.) Why does the ConcurrentModificationException not get thrown when multiple threads together modify a noncurrent collection while iterating over it in an enhanced for loop, but the exception gets thrown when the single main method’s thread does this? Even if the program structure, in which multiple threads modify a noncurrent collection, is not a loop (e.g., even if it is a simple sequence structure), my understanding is that this exception needs or should be thrown, i.e., if such a nonconcurrent collection is being modified by multiple threads non-thread-safely; furthermore, as I understand, a way to preclude the throwing of this exception is by replacing the nonconcurrent collection with a concurrent one, thereby enabling the multiple threads to thread-safely modify the replacement concurrent collection. Is this understanding that I have inaccurate?
(2.) Since the ConcurrentModificationException gets thrown when a single thread (i.e., the single main method’s thread) modifies a collection while iterating over it using an enhanced for loop, why doesn’t the exception get thrown when either of the other two threads alone does this?
This exception is horribly misnamed. It gets thrown in just one situation - if you are using an Iterator (or ListIterator), and are modifying the backing collection or map through any means other than the (List)Iterator. It doesn't matter if you do it concurrently or not, as you have seen. A better name would have been ModificationOutsideIteratorException, or something like that.
Some concurrent collections use techniques that prevent this, but not all. CopyOnWriteArrayList does this by using a snapshot to iterate over; if the list is modified, the snapshot stays intact. You're actually iterating over something that may be out-of-date. Vector on the other hand does not prevent this, and will still throw a ConcurrentModificationException.
Rob Spoor wrote:It gets thrown in just one situation - if you are using an Iterator (or ListIterator), and are modifying the backing collection or map through any means other than the (List)Iterator.
That's not true, really. Looking at ArrayList as an example, there are other situations that can result in a ConcurrentModificationException without any use of an Iterator. As an example, if you call add() while equals() is running. However, I think that all the other cases are probably impossible in single-threaded code. You've identified the most commonly encountered cause, and the only one I know of that can happen with single threading.
You're right. I should have said "if you're iterating". equals, hashCode, toArray and the bulk calls (containsAll, addAll, removeAll, removeIf, retainAll) usually all have hidden iterations. Some are even implemented with an Iterator; in Java 8, equals and hashCode also used an Iterator.