基于不断变化的地图对数组进行排序

时间:2014-07-27 00:20:55

标签: java concurrency

我有一个ConcurrentHashMap,它被异步更新以镜像数据库中的数据。我试图根据这些数据对数组进行排序,这些数据大部分时间都能正常工作,但如果数据在排序时更新,那么事情就会变得混乱。

我想过复制地图然后用复制的地图排序,但由于我需要排序的频率和地图的大小,这是不可能的。

1 个答案:

答案 0 :(得分:4)

我不确定我是否理解你的要求,所以我会处理两个不同的案例。 假设您的异步“更新”操作要求您更新地图中的2个键。 场景1是:如果两个更新中只有一个可见,则发生“排序”操作是可以的。 场景2是:您需要同时显示2个更新或根本不显示(称为原子行为)。

案例1:您不需要原子批量更新

在这种情况下,ConcurrentHashMap是可以的,看到迭代器保证在修改地图时不会失败。来自ConcurrentHashMap文档(强调我的):

  

类似地,Iterators和Enumerations在或从创建迭代器/枚举的某个点返回反映哈希表状态的元素。它们不会抛出ConcurrentModificationException。但是,迭代器被设计为一次只能由一个线程使用。

因此,即使在修改地图时也可以保证您可以遍历地图,而不会因为并发修改而导致迭代崩溃。但是(参见重点)你不能保证所有同时对地图进行的修改都是立即可见的,即使它们只是部分修改,也不是以哪种顺序。

案例2:您需要批量更新为原子

使用ConcurrentHashMap进一步了解,您无法保证批量操作(putAll)会以原子方式运行:

  

对于诸如putAll和clear之类的聚合操作,并发检索可能反映只插入或删除一些条目。

所以我看到了两种处理这种情况的方案,每种情况都需要锁定。

解决方案1:构建副本

构建“冻结”副本可以帮助您只有这个副本是在锁定所有其他更新的阶段构建的,因为复制地图意味着迭代它,我们的假设是如果我们进行并发修改,那么迭代是不安全的。

这可能看起来像:

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秒之后。 因此,您的读取操作取决于地图的“冻结副本”,实际每1(或2,或5或10)秒(或更新事件)更新一次,而不是每次都可能是您的情况。

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

锁定地图而不复制它是一种解决方案。您需要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,可以同时进行多次读取,也可以单次写入,但不能进行多次写入,也不能进行读写操作。 您必须考虑使用锁的公平变体的可能性,以便确保每个读写过程最终都有可能被执行,具体取决于您的使用模式。

(注意:如果你使用Locking / synchronized,你的Map可能不需要是并发的,因为写和读操作将是独占的,但这是另一个主题。)