Search code examples
javaspring-bootconcurrencyreentrantreadwritelock

Accessing map gives java.util.ConcurrentModificationException although map is updated using ReentrantReadWriteLock


We have a spring boot service that simply provides data from a map. The map is updated regularly, triggered by a scheduler, meaning we build a new intermediate map loading all the data needed and as soon as it is finished we assign it. To overcome concurrency issues we introduced a ReentrantReadWriteLock that opens a write lock just in the moment the assignment of the intermediate map happens and of course read locks while accessing the map. Please see simplified code below

@Service
public class MyService {

  private final Lock readLock;
  private final Lock writeLock;

  private Map<String, SomeObject> myMap = new HashMap<>();

  public MyService() {
    final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    readLock = rwLock.readLock();
    writeLock = rwLock.writeLock();
  }

  protected SomeObject getSomeObject(String key) {
    readLock.lock();
    try {
        return this.myMap.get(key);
      }
    } finally {
      readLock.unlock();
    }
    return null;
  }

  private void loadData() {

    Map<String, SomeObject> intermediateMyMap = new HashMap<>();

    // Now do some heavy processing till the new data is loaded to intermediateMyMap

    //clear maps
    writeLock.lock();
    try {
      myMap = intermediateMyMap;
    } finally {
      writeLock.unlock();
    }
  }
}

If we set the service under load accessing the map a lot we still saw the java.util.ConcurrentModificationException happening in the logs and I have no clue why.

BTW: Meanwhile I also saw this question, which seems also to be a solution. Nevertheless, I would like to know what I did wrong or if I misunderstood the concept of ReentrantReadWriteLock

EDIT: Today I was provided with the full stacktrace. As argued by some of you guys, the issue is really not related to this piece of code, it just happened coincidently in the same time the reload happened. The problem actually was really in the access to getSomeObject(). In the real code SomeObject is again a Map and this inner List gets sorted each time it is accessed (which is bad anyways, but that is another issue). So basically we ran into this issue


Solution

  • I see nothing obviously wrong with the code. ReadWriteLock should provide the necessary memory ordering guarantees (See Memory Synchronization section at https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html)

    The problem might well be in the "heavy processing" part. A ConcurrentModificationException could also be caused by modifying the map while iterating over it from a single thread, but then you would see the same problem regardless of the load on the system.

    As you already mentioned, for this pattern of replacing the whole map I think a volatile field or an AtomicReference would be the better and much simpler solution.