0

我们有一个 Spring Boot 服务,它只是提供来自地图的数据。地图会定期更新,由调度程序触发,这意味着我们构建一个新的中间地图,加载所有需要的数据,一旦完成,我们就会分配它。为了克服并发问题,我们引入了一个 ReentrantReadWriteLock,它在中间映射分配发生的那一刻打开一个写锁,当然在访问映射时打开一个读锁。请参阅下面的简化代码

@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();
    }
  }
}

如果我们将服务设置为负载访问地图很多,我们仍然会在日志中看到 java.util.ConcurrentModificationException 发生,我不知道为什么。

BTW:同时我也看到了这个问题,这似乎也是一个解决方案。不过,我想知道我做错了什么,或者我是否误解了 ReentrantReadWriteLock 的概念

编辑:今天我得到了完整的堆栈跟踪。正如你们中的一些人所说,这个问题实际上与这段代码无关,它只是在重新加载发生的同时巧合发生。问题实际上在于对 getSomeObject() 的访问。在实际代码中, SomeObject 又是一个 Map ,并且每次访问这个内部 List 时都会对其进行排序(无论如何这都很糟糕,但这是另一个问题)。所以基本上我们遇到了这个问题

4

2 回答 2

2

我发现代码没有明显错误。ReadWriteLock应提供必要的内存排序保证(请参阅https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html上的内存同步部分)

问题很可能出在“繁重的处理”部分。AConcurrentModificationException也可能是由于在从单个线程迭代映射时修改映射引起的,但是无论系统上的负载如何,您都会看到相同的问题。

正如您已经提到的,对于这种替换整个地图的模式,我认为 volatile 字段或 anAtomicReference将是更好、更简单的解决方案。

于 2019-09-03T14:14:11.563 回答
1

ReentrantReadWriteLock 只保证在 map 上持有锁的线程可以在需要时持有锁。

它不保证myMap没有被后台缓存。

缓存的值可能会导致读取过时。

过时的阅读会给你java.util.ConcurrentModificationException

需要声明 myMapvolatile以使更新对其他线程可见。

来自 Java 并发实践:

volatile 变量,以确保对变量的更新可预测地传播到其他线程。当一个字段被声明为 volatile 时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存操作重新排序。易失性变量不会缓存在寄存器或缓存中,它们对其他处理器是隐藏的,因此对易失性变量的读取总是返回任何线程最近的写入。

皮尔斯,蒂姆。Java 并发实践

另一种方法是使用syncronizedongetSomeObject和一个同步块 on thisaroundmyMap = intermediateMyMap;

于 2019-09-04T13:26:00.337 回答