How to Fix ConcurrentModificationException in Java Multithreading

Snippet of programming code in IDE
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:

  1. 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.

  2. 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!