Java 资源节约内存上的读写锁

Java Read Write Locks on a resource conserving memory

内存中有一大堆 R 类型的对象。修改对象需要写锁,读需要读锁。我可以将 ReadWriteLock 存储为 class R 的私有成员,但是,我想节省内存。在任何时候,只有一小部分对象被修改或读取。有多种方法可以决定不存储特定资源的读写锁(例如,如果它在一段时间内没有被读取或写入,t)。出于此问题的目的,假设可以定期确定可以删除资源的锁。但是,请记住,当在一个线程中删除资源锁时,一个或多个其他线程可能会尝试修改或读取该资源。所有这一切都发生在多线程环境中。您将如何以最少的锁定量实现它?

例如,一种方法是将读写锁存储在并发映射中:

Map<R,ReadWriteLock> map = new ConcurrentHashMap<>();

当确定可以删除资源的读写锁时,将其从映射中移除。但是也有可能如前所述,在决定删除条目之后,删除条目之前,其他线程可能想要获取读锁或写锁。

你可能认为 computeifabsentremove 的组合可以使用。但是,这不起作用。例如:

//--Thread1 write lock--
ReadWriteLock rwl = map.computeIfAbsent(r, r -> new ReadWriteLock()); // 1
rwl.writeLock.lock();                                        // 4
//Modify r here

//--Thread2: Removing entry--
map.remove(r);                                               // 2

//Thread3: write lock
ReadWriteLock rwl = map.computeIfAbsent(r, r-> new ReadWriteLock()); // 3
rwl.writeLock.lock();                                        // 5
//Modify r here.

问题是线程 1 的锁对象与线程 3 获得的锁不同,错误地允许两个写入同时发生。右边的数字表示执行顺序。

答案不需要使用上面示例中给出的并发映射,但这似乎是一个好的开始,并提供对锁的并发访问。如果您确实使用并发映射,请随意将 ReadWriteLock 包装在另一个结构中或创建您自己的 ReadWriteLock 版本。

总而言之,问题是如何维护资源集合的读写锁,而不必为集合中的每个对象存储读写锁并最大限度地减少锁争用。

the question is how to maintain read write locks for a collection of resources without having to store a read write lock for every object in the collection and minimizing lock contention

你考虑过使用条纹锁吗? (例如,https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Striped.html

基本上,它是 M 数据的 N 个锁的集合,其中 N < M。散列函数用于将任何给定数据的身份映射到控制它的锁​​。

您可以使用方法 computecomputeIfPresent 来发挥您的优势。重要的是 adding/locking/removing 消费者内部以原子方式完成它。

注意:您在示例中使用了 putIfAbsent,但是 returns 之前 分配的值,而不是 赋值.

public static class Locks<R>
{
    private ConcurrentHashMap<R, ReentrantReadWriteLock> locks = new ConcurrentHashMap<>();

    public void lock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.compute(r, (key, lock) -> {
            ReentrantReadWriteLock actualLock = lock == null ? new ReentrantReadWriteLock() : lock;
            whichLock.apply(actualLock).lock();
            return actualLock;
        });
    }

    public void unlock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.computeIfPresent(r, (key, lock) -> {
            whichLock.apply(lock).unlock();
            return lock; // you could return null here if lock is unlocked (see cleanUp) to remove it immediately
        });
    }

    public void cleanUp()
    {
        for (R r : new ArrayList<>(locks.keySet()))
        {
            locks.computeIfPresent(r, (key, lock) -> locks.get(r).isWriteLocked()
                                                     || locks.get(r).getReadLockCount() != 0 ? lock : null);
        }
    }
}

注意我是如何使用的

  • compute in lock 创建新锁并立即锁定
  • computeIfPresent in unlock 检查是否有锁
  • computeIfPresent in cleanUp 在我检查读锁计数时检查是否需要一个锁而没有另一个线程锁定写锁

现在,unlock 没什么用(空值检查除外,这只是一种预防措施)。在 unlock 中返回 null 会很好地清除不必要的锁并使 cleanUp 过时,但可能会增加创建新锁的需要。这取决于锁的使用频率。

当然,您可以为 read/write 添加便捷方法,而不必提供 getter whichLock.