Blog Concurrency and Multithreading Java

Mastering Java Collections: Avoid ConcurrentModificationException During HashMap Iteration – Unveiling the Inner Workings of Hash Tables and Iterators 🚀

Introduction:

In the world of Java programming🌐, collections play a vital role in managing and manipulating data. Among these collections, HashMaps are widely used for their efficient key-value storage and retrieval capabilities. However, when working with HashMaps (or any other collection type), developers often encounter a perplexing exception: ConcurrentModificationException 😵.

This exception can occur when attempting to modify a collection while iterating over it, leaving many developers scratching their heads🤔🤒.

In this comprehensive blog post, we’ll dive deep into the inner workings of HashMaps, iterators, and the reasons behind the ConcurrentModificationException. We’ll also explore safe techniques for modifying HashMaps during iteration, empowering you to write robust and efficient Java code💪.

Understanding the ConcurrentModificationException:

The ConcurrentModificationException is a RuntimeException thrown by Java’s collections framework when a structural modification is detected during an iteration operation. This exception serves as a fail-fast mechanism⚡, designed to prevent unexpected behavior and potential data corruption.

It occurs when an iterator’s view of the collection becomes inconsistent with the actual state of the collection due to concurrent modifications made outside of the iterator’s control.

Example Illustration:

Let’s assume we have a HashMap<Integer, String> to represent student IDs and their names. We want to remove entries based on some conditions while iterating over the map.

Scenario 1: Using phoneBook.remove()

Java
HashMap<Integer, String> students = new HashMap<>();
students.put(1, "Alice");
students.put(2, "Bob");
students.put(3, "Charlie");

for (Integer id : students.keySet()) {
    if (id.equals(2)) {
        students.remove(id); // This can cause ConcurrentModificationException
    }
}

In this scenario, directly calling remove() on students while iterating over its keySet triggers a ConcurrentModificationException because the structural modification is not being communicated to the iterator controlling the loop.

The iterator detects that the collection has been modified outside its control and fails fast by throwing this exception.

Scenario 2: Using keyIterator.remove()

Java
HashMap<Integer, String> students = new HashMap<>();
students.put(1, "Alice");
students.put(2, "Bob");
students.put(3, "Charlie");

Iterator<Integer> it = students.keySet().iterator();
while (it.hasNext()) {
    Integer id = it.next();
    if (id.equals(2)) {
        it.remove(); // Safely removes the current element without causing ConcurrentModificationException
    }
}

Here, using it.remove() correctly informs the iterator about the structural modification. The iterator is designed to handle such removals safely without invalidating its state, thus preventing ConcurrentModificationException 🎉.

Understanding the Internal Structure of HashMaps🏗️:

To fully grasp the reasons behind the ConcurrentModificationException and the safe removal techniques, it’s essential to understand the internal structure of HashMaps and how iterators work🧠.

HashMap is an implementation of the Map interface in Java, which stores key-value pairs🔑. Internally, HashMap uses a data structure called a hash table, which consists of an array of buckets (or slots). Each bucket can hold one or more key-value pairs 📦.

Here’s a visual representation of a hash table with four buckets📊:

Index:   0    1    2    3
Bucket: |A|->|B| |C| |D|

Each index in the array corresponds to a hash code (calculated after applying a modulo operation with the array size)🧮.

Entries (e.g., “A”, “B”, “C”, “D”) are placed in buckets based on their hash code. If multiple entries have the same hash code (known as a collision), they are organized within the same bucket using a linked list or a tree structure (for performance optimization in certain scenarios) 🌳.

When an entry is removed from the HashMap🗑️:

  • Direct remove(): If the remove() method is called directly on the HashMap (or any structural modification is made), the internal structure of the hash table changes. This could involve changing the links in the bucket’s linked list or tree, reducing the size, and potentially rehashing or redistributing entries (especially in cases of insertions)🔄.

    These modifications are not known to an active iterator, which expects the structure to remain constant during its operation ⏳.
  • Iterator remove(): This method is designed to be aware of the iteration process. It modifies the collection in a way that’s consistent with the current progress of the iteration, updating the internal state of the iterator to reflect the removal. This ensures that the iterator does not lose its place or encounter unexpected structural changes in the collection🔄.

The Fail-Fast Mechanism⚡:

Java’s collections framework implements a fail-fast mechanism to detect concurrent modifications during iteration. This mechanism is designed to prevent unexpected behaviour and potential data corruption that could occur if an iterator is allowed to continue iterating over a collection that has been modified concurrently 🚫.

When an iterator is created for a collection, it captures a modCount value, which represents the number of structural modifications that have been made to the collection. During each iteration step, the iterator checks if the modCount value of the collection has changed. If it has, it means that a concurrent modification has occurred, and the iterator throws a ConcurrentModificationException to fail fast and prevent further iteration🛑.

This fail-fast mechanism ensures that iterators remain consistent with the state of the collection they are iterating over. However, it also means that developers need to be cautious when modifying collections during iteration, as unexpected modifications can lead to the ConcurrentModificationException 😱.

Safe Modification Techniques🛠️:

While the ConcurrentModificationException may seem like a nuisance at first, it is a safeguard that helps maintain data integrity and prevent unexpected behaviour in your code. However, there are situations where you may need to modify a collection while iterating over it. In such cases, you have several safe techniques at your disposal:

  1. Use Iterator.remove(): As demonstrated earlier, using the remove() method provided by the iterator is a safe way to remove elements from a collection during iteration. The iterator internally updates its state to reflect the removal, ensuring consistency✔️.
  2. Create a copy of the collection: If you need to perform multiple modifications or complex operations during iteration, you can create a copy of the collection and iterate over the copy instead. This way, you can modify the original collection without affecting the iteration process🔄.
  3. Use a separate collection to store modifications: Another approach is to create a separate collection (e.g., a List or Set) to store the elements you want to remove or modify. After the iteration is complete, you can apply the modifications to the original collection based on the elements stored in the separate collection.
  4. Use Java 8 Streams and Lambda Expressions: Java 8 introduced streams and lambda expressions, which provide a more functional approach to working with collections. Streams allow you to perform operations on collections without modifying the original data source, reducing the risk of encountering ConcurrentModificationExceptions.
  5. Use Concurrent Collections: If you’re working in a multi-threaded environment, consider using thread-safe concurrent collections provided by the java.util.concurrent package. These collections are designed to handle concurrent modifications safely, reducing the risk of encountering ConcurrentModificationExceptions.

Visualizing the Internal Structure of a HashMap:

To better understand how the internal structure of a HashMap changes during modifications and iterations, let’s visualize the process using an example.

Suppose we have a HashMap with the following key-value pairs:

Java
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 35);

Initially, the hash table might look like this:

Java
Index:  0    1    2    3    4    5    6    7    8    9
Bucket: |A|->|B|->|C|

Here, all three entries (“Alice”, “Bob”, and “Charlie”) have been placed in the same bucket due to hash collisions. They are organized as a linked list within the bucket.

Now, let’s iterate over the HashMap using an iterator and try to remove the entry for “Bob”:

Java
Iterator<Map.Entry<String, Integer>> it = ages.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> entry = it.next();
    if (entry.getKey().equals("Bob")) {
        it.remove(); // Safe removal using the iterator
    }
}

When the iterator reaches the entry for “Bob”, it safely removes it from the linked list within the bucket. The internal structure of the hash table is updated accordingly:

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over