Java读写资源保存内存上的锁

时间:2021-11-25 09:34:41

In memory is a large collection of objects of type R. To modify an object requires a write lock and to read requires a read lock. I could store a ReadWriteLock as a private member of the class R, however, I want to conserve memory. At any time, only a small percentage of the objects are being modified or read. There are various ways to decide to not store a read write lock for a particular resource (for example, if it has not be read or written to for some amount of time, t). For purposes of this question assume that periodically it can be determined that the lock for the resource can be deleted. However, keep in mind that while the lock for the resource is being deleted in a thread, one or more other threads may attempt to modify or read the resource. All this is occuring in a multithreaded environment. How would you implement this with the least amount of locking?

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

For example, one way to do this is to store the read write locks in a concurrent map:

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

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

When it is determined that the read write lock for a resource can be deleted then remove it from the map. However, it may be possible as mentioned above that after it has been decided to delete the entry and before the entry is deleted, that other threads may want to acquire a read or write lock.

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

You may think that a combination of computeifabsent and remove can be used. However, that does not work. For example:

您可能认为可以使用computeifabsent和remove的组合。但是,这不起作用。例如:

//--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.

The problem is that the lock object by thread 1 will not be the same as the lock obtained by thread 3 and incorrectly allowing two writes to occur at the same time. The numbers on the right show the order of execution.

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

An answer need not use a concurrent map as given in the example above but it seems to be a good start and provides concurrent access to locks. If you do use a concurrent map feel free to wrap the ReadWriteLock in another structure or to create your own version of ReadWriteLock.

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

In summary 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.

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

2 个解决方案

#1


1  

You can use the methods compute and computeIfPresent to your advantage. The important thing is to do the adding/locking/removing inside the consumers to have it done atomically.

您可以使用compute和computeIfPresent方法。重要的是在消费者中进行添加/锁定/移除以使其以原子方式完成。

Note: you used putIfAbsent in your example, but that returns the previously asigned value, not the newly assigned value.

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

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

Note how I use

请注意我的使用方法

  • compute in lock to create new locks and lock them immediately
  • 计算锁定以创建新锁并立即锁定它们

  • computeIfPresent in unlock to check whether there is a lock at all
  • 在解锁时使用computeIfPresent来检查是否存在锁定

  • computeIfPresent in cleanUp to check whether a lock is needed without another thread locking write lock while I am checking read lock count
  • cleanIp中的computeIfPresent用于检查是否需要锁定,而在检查读取锁定计数时没有另一个线程锁定写入锁定

Right now, unlock is rather useless (except for null checks, which is just a precaution). Returning null in unlock would nicely clean up unnecessary locks and make cleanUp obsolete, but might increase the need for new locks being created. This depends on how frequently locks are used.

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

You could add convenience methods for read/write, of course, instead of having to provide the getter whichLock.

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

#2


1  

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

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

Have you considered using striped locks? (e.g., https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Striped.html)

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

Basically, it's a collection of N locks for M data where N < M. A hash function is used to map the identity of any given datum to the lock that controls it.

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

#1


1  

You can use the methods compute and computeIfPresent to your advantage. The important thing is to do the adding/locking/removing inside the consumers to have it done atomically.

您可以使用compute和computeIfPresent方法。重要的是在消费者中进行添加/锁定/移除以使其以原子方式完成。

Note: you used putIfAbsent in your example, but that returns the previously asigned value, not the newly assigned value.

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

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

Note how I use

请注意我的使用方法

  • compute in lock to create new locks and lock them immediately
  • 计算锁定以创建新锁并立即锁定它们

  • computeIfPresent in unlock to check whether there is a lock at all
  • 在解锁时使用computeIfPresent来检查是否存在锁定

  • computeIfPresent in cleanUp to check whether a lock is needed without another thread locking write lock while I am checking read lock count
  • cleanIp中的computeIfPresent用于检查是否需要锁定,而在检查读取锁定计数时没有另一个线程锁定写入锁定

Right now, unlock is rather useless (except for null checks, which is just a precaution). Returning null in unlock would nicely clean up unnecessary locks and make cleanUp obsolete, but might increase the need for new locks being created. This depends on how frequently locks are used.

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

You could add convenience methods for read/write, of course, instead of having to provide the getter whichLock.

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

#2


1  

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

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

Have you considered using striped locks? (e.g., https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Striped.html)

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

Basically, it's a collection of N locks for M data where N < M. A hash function is used to map the identity of any given datum to the lock that controls it.

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