首页 > 解决方案 > Java 对资源节约内存的读写锁定

问题描述

内存中有大量R类型的对象。修改对象需要写锁,读需要读锁。我可以将 ReadWriteLock 存储为类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 版本。

总之,问题是如何维护资源集合的读写锁,而不必为集合中的每个对象存储读写锁并最小化锁争用。

标签: javamultithreadingconcurrencylockingconcurrenthashmap

解决方案


您可以使用这些方法computecomputeIfPresent发挥自己的优势。重要的是在消费者内部进行添加/锁定/删除以自动完成。

注意:您putIfAbsent在示例中使用了,但返回的是先前分配的值,而不是分配的值。

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

注意我如何使用

  • computeinlock创建新锁并立即锁定它们
  • computeIfPresentunlock检查是否有锁
  • computeIfPresentcleanUp我检查读锁计数时检查是否需要锁而不需要另一个线程锁定写锁

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

当然,您可以添加方便的读/写方法,而不必提供 getter whichLock


推荐阅读