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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java读写资源保存内存上的锁相关的知识,希望对你有一定的参考价值。

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

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

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

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

您可能认为可以使用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.

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

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

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

答案

你可以使用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);
        }
    }
}

请注意我的使用方法

  • computelock创建新的锁并立即锁定它们
  • computeIfPresentunlock检查是否有锁
  • computeIfPresent中的cleanUp检查是否需要锁定而没有另一个线程锁定写锁定而我正在检查读取锁定计数

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

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

另一答案

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

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

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

以上是关于Java读写资源保存内存上的锁的主要内容,如果未能解决你的问题,请参考以下文章

java中ReentrantReadWriteLock读写锁的使用

Java高并发编程实战1,那些年学过的锁

java中的锁

多线程与并发:Java中的锁

线程同步-使用ReaderWriterLockSlim类

读写锁