Why is a ConcurrentModificationException thrown and how to debug it
I am using a Collection
(a HashMap
used indirectly by the JPA, it so happens), but apparently randomly the code throws a ConcurrentModificationException
. What is causing it and how do I fix this problem? By using some synchronization, perhaps?
Here is the full stack-trace:
Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$ValueIterator.next(Unknown Source)
at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
This is not a synchronization problem. This will occur if the underlying collection that is being iterated over is modified by anything other than the Iterator itself.
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Entry item = it.next();
map.remove(item.getKey());
}
This will throw a ConcurrentModificationException
when the it.hasNext()
is called the second time.
The correct approach would be
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Entry item = it.next();
it.remove();
}
Assuming this iterator supports the remove()
operation.
Try using a ConcurrentHashMap
instead of a plain HashMap
Modification of a Collection
while iterating through that Collection
using an Iterator
is not permitted by most of the Collection
classes. The Java library calls an attempt to modify a Collection
while iterating through it a "concurrent modification". That unfortunately suggests the only possible cause is simultaneous modification by multiple threads, but that is not so. Using only one thread it is possible to create an iterator for the Collection
(using Collection.iterator()
, or an enhanced for
loop), start iterating (using Iterator.next()
, or equivalently entering the body of the enhanced for
loop), modify the Collection
, then continue iterating.
To help programmers, some implementations of those Collection
classes attempt to detect erroneous concurrent modification, and throw a ConcurrentModificationException
if they detect it. However, it is in general not possible and practical to guarantee detection of all concurrent modifications. So erroneous use of the Collection
does not always result in a thrown ConcurrentModificationException
.
The documentation of ConcurrentModificationException
says:
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible...
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception...
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.
Note that
- the exception may be throw, not must be thrown
- different threads are not required
- throwing the exception cannot be guaranteed
- throwing the exception is on a best-effort basis
- throwing the exception happens when the concurrent modification is detected, not when it is caused
The documentation of the HashSet
, HashMap
, TreeSet
and ArrayList
classes says this:
The iterators returned [directly or indirectly from this class] are fail-fast: if the [collection] is modified at any time after the iterator is created, in any way except through the iterator's own remove method, the
Iterator
throws aConcurrentModificationException
. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw
ConcurrentModificationException
on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
Note again that the behaviour "cannot be guaranteed" and is only "on a best-effort basis".
The documentation of several methods of the Map
interface say this:
Non-concurrent implementations should override this method and, on a best-effort basis, throw a
ConcurrentModificationException
if it is detected that the mapping function modifies this map during computation. Concurrent implementations should override this method and, on a best-effort basis, throw anIllegalStateException
if it is detected that the mapping function modifies this map during computation and as a result computation would never complete.
Note again that only a "best-effort basis" is required for detection, and a ConcurrentModificationException
is explicitly suggested only for the non concurrent (non thread-safe) classes.
Debugging ConcurrentModificationException
So, when you see a stack-trace due to a ConcurrentModificationException
, you can not immediately assume that the cause is unsafe multi-threaded access to a Collection
. You must examine the stack-trace to determine which class of Collection
threw the exception (a method of the class will have directly or indirectly thrown it), and for which Collection
object. Then you must examine from where that object can be modified.
- The most common cause is modification of the
Collection
within an enhancedfor
loop over theCollection
. Just because you do not see anIterator
object in your source code does not mean there is noIterator
there! Fortunately, one of the statements of the faultyfor
loop will usually be in the stack-trace, so tracking down the error is usually easy. - A trickier case is when your code passes around references to the
Collection
object. Note that unmodifiable views of collections (such as produced byCollections.unmodifiableList()
) retain a reference to the modifiable collection, so iteration over an "unmodifiable" collection can throw the exception (the modification has been done elsewhere). Other views of yourCollection
, such as sub lists,Map
entry sets andMap
key sets also retain references to the original (modifiable)Collection
. This can be a problem even for a thread-safeCollection
, such asCopyOnWriteList
; do not assume that thread-safe (concurrent) collections can never throw the exception. - Which operations can modify a
Collection
can be unexpected in some cases. For example,LinkedHashMap.get()
modifies its collection. - The hardest cases are when the exception is due to concurrent modification by multiple threads.
Programming to prevent concurrent modification errors
When possible, confine all references to a Collection
object, so its is easier to prevent concurrent modifications. Make the Collection
a private
object or a local variable, and do not return references to the Collection
or its iterators from methods. It is then much easier to examine all the places where the Collection
can be modified. If the Collection
is to be used by multiple threads, it is then practical to ensure that the threads access the Collection
only with appropriate synchonization and locking.
In Java 8, you can use lambda expression:
map.keySet().removeIf(key -> key condition);
It sounds less like a Java synchronization issue and more like a database locking problem.
I don't know if adding a version to all your persistent classes will sort it out, but that's one way that Hibernate can provide exclusive access to rows in a table.
Could be that isolation level needs to be higher. If you allow "dirty reads", maybe you need to bump up to serializable.