4 Essential Techniques for Tackling Concurrency: How to Fix the ConcurrentModificationException
Concurrency Made Manageable: How To Fix the ConcurrentModificationException
Concurrency can be a minefield.
As you navigate this minefield, you’ll likely encounter the dreaded ConcurrentModificationException
. Don’t panic just yet. This exception is common and it is easily fixable, once you properly understand it.
Let’s look at what the exception means, the common causes of the exception, and, most importantly, what can be done to fix the ConcurrentModificationException
.
What is a ConcurrentModificationException?
A ConcurrentModificationException
is a RuntimeException
that is thrown when a collection is modified while it is being iterated over.
This often happens when you try to add or remove elements from a collection, like a list, during iteration. Doing so can lead to unpredictable results and potential bugs.
Under the hood, this exception is triggered by the fail-fast behaviour of Java’s collection classes. The fail-fast mechanism detects when the structure of the collection has been changed during iteration and throws the ConcurrentModificationException
to prevent further iteration and potential issues.
What causes a ConcurrentModificationException?
The ConcurrentModificationException typically occurs in the following scenarios:
1. Modifying a collection directly while you’re iterating over it using an Iterator or a for-each loop
// Removing an element from the list
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
for (String name : names) {
if (name.equals("Bob")) {
names.remove(name); // Throws ConcurrentModificationException
}
}
// Adding an element to the list
for (String name : names) {
if (name.equals("Bob")) {
names.add("David"); // unsafe modification
}
}
2. Modifying a collection in a multi-threaded environment, where one thread iterates over the collection while another thread modifies it.
List<String> sharedList = new ArrayList<>(Arrays.asList("One", "Two", "Three"));
// Thread 1
new Thread(() -> {
for (String item : sharedList) {
System.out.println(item);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Thread 2
new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedList.add("Four"); // Throws ConcurrentModificationException in Thread 1
}).start();
How to fix ConcurrentModificationException
As you can see above, the ConcurrentModifictaionException
is normally thrown when you try to change a Collection while iterating over it.
Luckily, we can use several methods to safely make changes to our collection while iterating over it, which won’t throw an exception.
1. Use the Iterator’s add() and/or remove() methods
One method is to make use of Java’s Iterator
interface and its add()
and remove()
methods to safely modify a collection during iteration.
Let’s start with adding elements to a collection. To do so, simply use the Iterator’s add() method, like so:
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.equals("Bob")) {
iterator.add("David"); // Safe addition
}
}
When you need to remove elements from a collection during iteration, use the remove()
method provided by the Iterator
interface. For example:
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.equals("Bob")) {
iterator.remove(); // Safe removal
}
}
2. Create a copy of the collection for iteration
Another technique you can use if you need to add or remove elements from a collection during iteration is to create a copy of the collection for iteration purposes.
This way, you can modify the original collection without affecting the iteration, like so:
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
List<String> namesCopy = new ArrayList<>(names);
for (String name : namesCopy) {
if (name.equals("Bob")) {
names.remove(name); // Modifies the original collection
}
}
However, keep in mind that creating a copy of the collection may not be the most efficient solution for larger collections, as it can consume additional memory and processing time.
3. Use Java 8 Streams and filter()
I’m a big fan of streams, so this is personally my go-to method when I need to iterate through an array and remove elements.
Java 8 introduced the Stream API, which provides a more functional approach to handling collections. With the Stream API, you can use the filter()
method to create a new collection without the elements you want to remove. This method does not modify the original collection, thus avoiding the ConcurrentModificationException
.
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
names = names.stream()
.filter(name -> !name.equals("Bob"))
.collect(Collectors.toList());
4. Use a Concurrent Collection
If you need to handle collections in a multi-threaded environment, consider using a concurrent collection, such as ConcurrentHashMap
, CopyOnWriteArrayList
, or CopyOnWriteArraySet
. These collections are designed to be safely modified by multiple threads without causing a ConcurrentModificationException
.
List<String> sharedList = new CopyOnWriteArrayList<>(Arrays.asList("One", "Two", "Three"));
// Thread 1
new Thread(() -> {
for (String item : sharedList) {
System.out.println(item);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Thread 2
new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedList.add("Four"); // Safe addition in a concurrent collection
}).start();
Keep in mind that concurrent collections may have slightly lower performance compared to their non-concurrent counterparts due to the overhead of synchronisation. Therefore, it’s best to only use them when necessary.
Use these 4 strategies to fix the ConcurrentModificationException
and safeguard your code from concurrency issues.
If you found this article helpful or informative, please consider clapping, sharing, checking out some of my other articles and following me on Medium and/or Twitter. Thanks a lot and happy reading!