In this article, we will explore ConcurrentModificationException in Java in great details. We will understand causes of ConcurrentModificationException and examine measures to avoid ConcurrentModificationException in Single Threaded and Multi-threaded environments. Finally, we will look into the best practices.
What is ConcurrentModificationException in Java?
ConcurrentModificationException
in Java is an exception that occurs in Java when attempting to modify a collection (such as a List, Set, or Map) while it is being iterated over using an iterator. It is an unchecked Exception.
This exception is designed to catch scenarios where the internal state of the collection has been changed by another thread or by the same thread, causing the iterator to become inconsistent with the collection’s modified state.
Causes of ConcurrentModificationException in Java
A ConcurrentModificationException
in Java is typically caused by modifying a collection while it’s being iterated over using an iterator. Here’s a code example to illustrate this:
import java.util.ArrayList;
import java.util.List;
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> myList = new ArrayList<>();
myList.add("Item 1");
myList.add("Item 2");
myList.add("Item 3");
// Attempt to modify the collection during iteration
for (String item : myList) {
myList.remove(item); // Causes ConcurrentModificationException
}
}
}
Output
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at ConcurrentModificationExample.main(ConcurrentModificationExample.java:12)
In this example, a List
called myList
is created with three items. The code then attempts to iterate over the list using an enhanced for loop and remove each item from the list while iterating. This results in a ConcurrentModificationException
because the collection is being modified during iteration.
To prevent this exception, you should avoid modifying a collection while iterating over it. Instead, use the iterator’s methods, such as remove()
, to modify the collection safely. Here’s an example using the iterator to remove elements:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ConcurrentModificationFixedExample {
public static void main(String[] args) {
List<String> myList = new ArrayList<>();
myList.add("Item 1");
myList.add("Item 2");
myList.add("Item 3");
// Use iterator to remove elements during iteration
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove(); // Removes the current element safely
}
}
}
In this corrected example, the iterator’s remove()
method is used to safely remove elements from the collection while iterating, preventing the ConcurrentModificationException
.
Avoiding ConcurrentModificationException in Single Threaded Environment
In a single-threaded environment, avoiding ConcurrentModificationException
is relatively straightforward. Since there are no other threads concurrently modifying the collection, you only need to be careful about modifying the collection while it’s being iterated. Here are a few approaches to avoid ConcurrentModificationException
in a single-threaded environment:
Use an Iterator: When you need to modify a collection while iterating over it, use an explicit Iterator
and its remove()
method to safely remove elements. This way, the iterator and the collection remain in sync.
List<String> myList = new ArrayList<>();
myList.add("Item 1");
myList.add("Item 2");
myList.add("Item 3");
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (shouldRemove(item)) {
iterator.remove(); // Safely remove elements
}
}
Use a Copy of the Collection:
Create a copy of the collection before iterating over it and perform modifications on the original collection. This prevents the iteration process from being affected by changes made during the iteration.
List<String> myList = new ArrayList<>();
myList.add("Item 1");
myList.add("Item 2");
myList.add("Item 3");
List<String> copyList = new ArrayList<>(myList);
for (String item : copyList) {
if (shouldRemove(item)) {
myList.remove(item); // Modify the original collection
}
}
Use a Stream with Filter: If your intention is to remove specific elements from the collection, you can use Java Streams with the filter()
operation to create a new list with the desired elements. This avoids modifying the original collection during iteration.
List<String> myList = new ArrayList<>();
myList.add("Item 1");
myList.add("Item 2");
myList.add("Item 3");
myList = myList.stream()
.filter(item -> !shouldRemove(item))
.collect(Collectors.toList()); // Create a new list without removed items
Avoiding ConcurrentModificationException in Multi-threaded Environment
In a multi-threaded environment, avoiding ConcurrentModificationException
becomes more complex due to the potential for multiple threads concurrently modifying the collection. Proper synchronization and coordination are essential to prevent this exception. Here are some strategies to avoid ConcurrentModificationException
in a multi-threaded environment:
Use Thread-Safe Collections: Java provides thread-safe collection classes in the java.util.concurrent
package, such as CopyOnWriteArrayList
, ConcurrentHashMap
, and ConcurrentLinkedQueue
. These collections are designed to handle concurrent modifications safely.
Synchronize Access: Use synchronization mechanisms like synchronized
blocks or methods to ensure that only one thread can modify the collection at a time. This prevents concurrent modifications that can lead to the exception.
List<String> myList = Collections.synchronizedList(new ArrayList<>());
synchronized (myList) {
// Modify myList safely
}
Use Locks: Utilize more advanced synchronization mechanisms like ReentrantLock
or ReadWriteLock
to control access to the collection. This allows for fine-grained control over read and write operations.
Iterate Safely with Copy: If you need to iterate over the collection and modify it, consider creating a copy of the collection for iteration. This ensures that the original collection isn’t modified during iteration.
Atomic Operations: If the modifications are simple and involve a single operation (e.g., adding an element), you can use atomic classes like AtomicReference
to update the collection atomically.
Use Thread-Local Variables: If different threads need to maintain separate collections, consider using thread-local variables to store individual collections for each thread.
Use volatile
or AtomicReference
for Shared References: If multiple threads share a reference to a collection, use the volatile
keyword or AtomicReference
to ensure proper visibility of changes.
Use Concurrency Utilities: Java provides higher-level concurrency utilities, such as ExecutorService
, to manage thread execution and coordination. Utilizing these can help avoid concurrency issues.
Best Practices
When working with collections in a multi-threaded environment, there are several best practices to follow to ensure proper synchronization, avoid exceptions like ConcurrentModificationException
, and maintain the correctness of your program. Here are some key best practices:
- Choose Thread-Safe Collections: Whenever possible, use thread-safe collections from the
java.util.concurrent
package, such asCopyOnWriteArrayList
,ConcurrentHashMap
, andConcurrentLinkedQueue
. These collections are designed to handle concurrent modifications safely without additional synchronization. - Use Explicit Synchronization: If you’re using standard collections like
ArrayList
, ensure proper synchronization usingsynchronized
blocks or methods when reading and modifying the collection. This prevents multiple threads from accessing the collection simultaneously. - Minimize Synchronized Blocks: Use synchronized blocks or methods only when necessary to avoid unnecessary contention and performance degradation. Keep the synchronized sections as short as possible.
- Separate Read and Write Operations: If multiple threads mostly read from the collection but rarely modify it, consider using a read-write lock (
ReadWriteLock
) to allow multiple threads to read concurrently while ensuring exclusive access during writes. - Avoid Modifying While Iterating: Do not modify a collection while iterating over it using a traditional for-each loop or an iterator. Instead, use thread-safe collections or synchronize appropriately to avoid
ConcurrentModificationException
. - Use Concurrent Data Structures: Choose the appropriate concurrent data structures based on your use case. For example, use
ConcurrentHashMap
for thread-safe maps, andConcurrentLinkedQueue
for thread-safe queues. - Atomic Operations: Utilize atomic classes like
AtomicInteger
,AtomicLong
, andAtomicReference
when performing simple operations that need to be thread-safe. These classes provide atomic read-modify-write operations. - Thread-Local Storage: When each thread needs its own copy of data, consider using thread-local variables. This prevents contention and eliminates the need for synchronization.
- Avoid Sharing Mutable State: Minimize sharing mutable objects between threads. Shared mutable state can lead to complex synchronization requirements and hard-to-debug issues.
- Use Concurrency Utilities: Utilize higher-level concurrency utilities like
ExecutorService
andExecutorCompletionService
to manage thread execution, coordination, and parallelism.
Conclusion: ConcurrentModificationException in Java
In this very comprehensive article on ConcurrentModificationException in Java, We explored in-depth on causes of ConcurrentModificationException, then looked into various ways of avoiding this kind of Exception in Single threaded and multi-threaded environments. Finally, we discussed, the best practices to follow to ensure proper synchronization, avoid exceptions like ConcurrentModificationException.
Read More : Common Unchecked Exceptions in Java