How to Fix ConcurrentModificationException in Java Multithreading
- Published on
How to Fix ConcurrentModificationException in Java Multithreading
When working with Java and multithreading, one of the common exceptions developers encounter is ConcurrentModificationException
. This exception often arises when a thread modifies a collection while another thread is iterating over it. In this blog post, we will explore the reasons behind this exception, discuss its implications in a multithreaded environment, and provide several strategies to resolve it.
Understanding ConcurrentModificationException
Java's collections framework does not support concurrent modification. This exception is thrown when a collection is structurally modified (e.g., adding or removing elements) during iteration. The exception serves as a fail-fast mechanism designed to help developers detect potential threading issues early in the development lifecycle.
Example of ConcurrentModificationException
Let's consider a simple example:
import java.util.ArrayList;
import java.util.List;
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
for (String s : list) {
System.out.println(s);
list.remove(s); // Modifying the list while iterating
}
});
thread1.start();
// Need to join to wait for thread1 to finish
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final list: " + list);
}
}
In the above code, running the main
method will lead to a ConcurrentModificationException
, as the list is being modified while it is being iterated over by thread1
.
Why Does ConcurrentModificationException
Occur?
When a thread modifies the structure of a collection, it invalidates the iterator that another thread might be using. This validity check is essential in avoiding unpredictable behavior and corrupted data states in a multithreaded environment.
Let’s dive deeper into understanding the context of use that can lead to this problem:
-
Iterators: Most collection classes provide Iterator objects, which allow you to traverse the collection. If the collection is changed while using an iterator, it may not reflect the latest state.
-
Thread Safety: Java is not inherently thread-safe. If multiple threads interact with the same collection simultaneously without necessary safeguards, exceptions and data corruption can occur.
Solutions to Avoid ConcurrentModificationException
1. Use Synchronized Collections
Java provides synchronized versions of collections, such as Collections.synchronizedList(...)
. This wraps a list, providing synchronized access.
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
public class SynchronizedCollectionExample {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
synchronized (list) { // Synchronize on the list to avoid modification exceptions
for (String s : list) {
System.out.println(s);
list.remove(s);
}
}
});
thread1.start();
}
}
2. Use Concurrent Collections
The Java Concurrent package offers thread-safe collections like CopyOnWriteArrayList
, which are designed for high concurrency scenarios.
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
for (String s : list) {
System.out.println(s);
// CopyOnWriteArrayList allows safe modification while iterating
list.remove(s);
}
});
thread1.start();
}
}
Using CopyOnWriteArrayList
allows safe iteration while modifying the collection without throwing ConcurrentModificationException
. This collection type creates a new copy of the underlying array every time a modification occurs.
3. Use External Synchronization
You can control access explicitly by synchronizing blocks of code to ensure thread safety during iteration or modification.
import java.util.ArrayList;
import java.util.List;
public class ExternalSynchronizationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
synchronized (list) {
for (String s : list) {
System.out.println(s);
list.remove(s);
}
}
});
thread1.start();
}
}
4. Use Java Streams
Another interesting solution involves using Java Streams, which inherently provide a more functional approach to data processing. Streams do not throw ConcurrentModificationException
when traversing collections.
import java.util.ArrayList;
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// Using Streams to process the elements
list.stream().filter(s -> !s.equals("B")).forEach(System.out::println); // Filters out "B"
// Safe as it doesn't modify the list while iterating
}
}
5. Concurrent HashMap
If you are specifically dealing with a map structure and require concurrent access, consider using ConcurrentHashMap
, which allows safe iteration while enabling modifications by multiple threads.
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
Thread thread1 = new Thread(() -> {
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
// ConcurrentHashMap allows safe modification
map.remove(key);
}
});
thread1.start();
}
}
The Closing Argument
ConcurrentModificationException
can be a daunting challenge when building multithreaded Java applications. However, understanding this exception along with implementing the necessary thread safety measures can help mitigate potential issues. Whether you choose synchronized collections, concurrent collections, external synchronization, or Java Streams, always think critically about how your choices will affect performance and data integrity.
Further Reading
By taking a well-informed approach, you can create robust Java applications that successfully handle multithreading issues without running into ConcurrentModificationException
. Happy coding!
Checkout our other articles