java - 根据不断变化的 map 对数组进行排序

标签 java concurrency

我有一个 ConcurrentHashMap,它会异步更新以镜像数据库中的数据。我正在尝试根据此数据对数组进行排序,该数据在大多数情况下都工作正常,但如果数据在排序时更新,那么事情可能会变得困惑。

我曾想过复制 map ,然后使用复制的 map 进行排序,但由于我需要排序的频率以及 map 的大小,这是不可能的。

最佳答案

我不确定我是否完全理解您的要求,因此我将处理两个单独的案例。 假设您的异步“更新”操作需要您更新 map 中的 2 个键。 场景 1 是:如果在两个更新中只有 1 个可见时发生“排序”操作,这是可以的。 场景 2 是:您需要 2 个更新同时可见或根本不可见(这称为原子行为)。

情况 1:您不需要原子批量更新

在这种情况下,ConcurrentHashMap 就可以了,因为迭代器保证在修改映射时不会失败。来自 ConcurrentHashMap 文档(重点是我的):

Similarly, Iterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. They do not throw ConcurrentModificationException. However, iterators are designed to be used by only one thread at a time.

因此,您可以保证即使在修改映射时也可以迭代该映射,而不会因并发修改而导致迭代崩溃。但是(请参阅重点)您不能保证同时对 map 进行的所有修改都立即可见,即使只有其中一部分以及按顺序可见。

情况 2:您需要批量更新是原子的

此外,对于 ConcurrentHashMap,您无法保证批量操作 (putAll) 将以原子方式运行:

For aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries.

因此,我看到了处理此案例的两种场景,每种场景都需要锁定。

解决方案 1:构建副本

构建“卡住”副本可以帮助您在所有其他更新都被锁定的阶段构建此副本,因为复制 map 意味着迭代它,我们的假设是如果我们有并发修改,那么迭代是不安全的。

这可能看起来像:

ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>(); //
AtomicReference<Map<String, String>> frozenCopy = new AtomicReference<Map<String, String>>(map); 

public void sortOperation() {
    sortUsingFrozenCopy();
}

public void updateOperation() {
    synchronized (map) { // Exclusive access to the map instance
        updateMap();
        Map<String, String> newCopy = new HashMap<String, String>();
        newCopy.putAll(map); // You build the copy. This is safe thanks to the exclusive access.
        frozenCopy.set(newCopy); // And you update the reference to the copy
    }
}

这个解决方案可以改进...... 看到您的 2 个操作(映射读取和映射写入)完全异步,可以假设您的读取操作无法知道(也不应该关心)前一个写入操作是在 0.1 秒之前发生还是在 0.1 秒之后发生。 因此,让您的读取操作依赖于 map 的“卡住副本”,该副本实际上每 1(或 2、5、10)秒(或更新事件)更新一次,而不是每次更新一次,这可能适合您的情况。

解决方案 2:锁定 map 以进行更新

锁定 map 而不复制它是一个解决方案。您需要一个 ReadWriteLock (或 Java 8 中的 StampedLock),以便可以进行多种排序,并互斥读写操作。

解决方案2实际上很容易实现。你会有类似的东西

ReadWriteLock lock = new ReentrantReadWriteLock();

public void sortOperation() {
    lock.readLock().lock();
            // read lock granted, which prevents writeLock to be granted
    try {
        sort(); // This is safe, nobody can write
    } finally {
        lock.readLock().unlock();
    }
}

public void updateOperation() {
    lock.writeLock().lock();
            // Write lock granted, no other writeLock (except to myself) can be granted
            // nor any readLock
    try {
        updateMap(); // Nobody is reading, that's OK.
    } finally {
        lock.writeLock().unlock();
    }
}

使用ReadWriteLock,可以同时进行多次读取,也可以进行一次写入,但不能进行多次写入或读取和写入。 您必须考虑使用锁的公平变体的可能性,以便您确定每个读写进程最终都有机会被执行,具体取决于您的使用模式。

(注意:如果您使用锁定/同步,您的 Map 可能不需要并发,因为写入和读取操作将是独占的,但这是另一个主题)。

关于java - 根据不断变化的 map 对数组进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24976746/

相关文章:

java - 跨线程的对象可见性

Java 并发 - 使用哪种技术来实现安全性?

java - 如何从Java中的多个线程获取和处理对象?如何创建 Future 的多个实例

java - 为什么看起来调用了错误的方法?

java - 模块和 JAR 文件有什么区别?

java - 如何以编程方式单击 JTabbedPane 中的选项卡?

java/jsf 重定向页面

sql-server - 与性能相关的 : How does SQL Server process concurrent queries from multiple connections from a single . NET 桌面应用程序?

java - 在数字中找到零

concurrency - 我如何在预览中构造带注释的 mainactor 的 swiftui 类