简体   繁体   English

尽管使用ReentrantReadWriteLock更新了地图,但访问地图会给出java.util.ConcurrentModificationException

[英]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. 我们有一个Spring Boot服务,它仅提供来自地图的数据。 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. 为了克服并发问题,我们引入了ReentrantReadWriteLock,它在发生中间映射的分配时立即打开写锁,当然在访问该映射时也会打开读锁。 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. 如果我们在访问地图的负载下设置服务很多,我们仍然会在日志中看到java.util.ConcurrentModificationException发生,我不知道为什么。

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 但是,我想知道自己做错了什么,或者是否误解了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(). 问题实际上出在对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). 在实际的代码中,SomeObject仍然是Map,并且每次访问该内部List时都会对其进行排序(无论如何这都是不好的,但这是另一个问题)。 So basically we ran into this issue 所以基本上我们遇到了这个问题

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 ) ReadWriteLock应该提供必要的内存顺序保证(请参阅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. ConcurrentModificationException也可能是由于在从单个线程对其进行迭代时修改映射而引起的,但是无论系统上的负载如何,您都将看到相同的问题。

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. 正如您已经提到的那样,对于这种替换整个地图的模式,我认为可变字段或AtomicReference将是更好,更简单的解决方案。

ReentrantReadWriteLock only guarantees the thread that holds the lock on the map can hold on to the lock if needed. ReentrantReadWriteLock仅保证在需要时将持有地图锁的线程保持在锁上。

It does not guarantee myMap has not been cached behind the scenes. 它不能保证myMap尚未在后台缓存。

A cached value could result in a stale read. 缓存的值可能会导致过时的读取。

A stale read will give you the java.util.ConcurrentModificationException 过时的读取将为您提供java.util.ConcurrentModificationException

myMap needs to be declared volatile to make the update visible to other threads. 需要将myMap声明为volatile以使更新对其他线程可见。

From Java Concurrency in Practice: 从实践中的Java并发:

volatile variables, to ensure that updates to a variable are propagated predictably to other threads. 易变变量,以确保对变量的更新可预测地传播到其他线程。 When a field is declared volatile, the compiler and runtime are put on notice that this variable is shared and that operations on it should not be reordered with other memory operations. 当将一个字段声明为volatile时,会通知编译器和运行时该变量是共享的,并且对该变量的操作不应与其他内存操作重新排序。 Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread. 易失性变量不会缓存在寄存器中或对其他处理器隐藏的缓存中,因此对易失性变量的读取始终会返回任何线程的最新写入。

Peierls, Tim. 蒂尔·佩尔斯 Java Concurrency in Practice 实践中的Java并发

an alternative would be to use syncronized on getSomeObject and a synchonized block on this around myMap = intermediateMyMap; 一个替代是使用syncronizedgetSomeObject并在synchonized块this围绕myMap = intermediateMyMap;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM