将Java读锁升级为写锁,用于Map中的缓存

发布于 2024-10-15 20:26:48 字数 1040 浏览 2 评论 0原文

我在下面的代码中遇到了死锁情况:

private static final ReadWriteLock opClassesLock = new ReentrantReadWriteLock();
private static final Map<Class<?>, ServiceClass> opClasses = new WeakHashMap<Class<?>, ServiceClass>();
public static ServiceClass get(Class<?> myClass) {
    opClassesLock.readLock().lock();
    try {
        ServiceClass op = opClasses.get(myClass);
        if (op == null) {
            opClassesLock.writeLock().lock(); // deadlock here
            try {
                op = new ServiceClass(myClass);
                opClasses.put(myClass, op);
            } finally {
                opClassesLock.writeLock().unlock();
            }
        }
        return op;
    } finally {
        opClassesLock.readLock().unlock();
    }
}

如果我检查了 ReentrantReadWriteLock 的文档,我就可以预测到这一点:

可重入还允许降级 从写锁到读锁,通过 获取写锁,然后 读锁,然后释放写锁 锁。 但是,从读取升级 锁不是写锁 可能。

除了使用单个锁而不是读/写锁(不允许并发读取)之外,还有其他方法可以解决此类问题吗?

I have a deadlock situation in the code below:

private static final ReadWriteLock opClassesLock = new ReentrantReadWriteLock();
private static final Map<Class<?>, ServiceClass> opClasses = new WeakHashMap<Class<?>, ServiceClass>();
public static ServiceClass get(Class<?> myClass) {
    opClassesLock.readLock().lock();
    try {
        ServiceClass op = opClasses.get(myClass);
        if (op == null) {
            opClassesLock.writeLock().lock(); // deadlock here
            try {
                op = new ServiceClass(myClass);
                opClasses.put(myClass, op);
            } finally {
                opClassesLock.writeLock().unlock();
            }
        }
        return op;
    } finally {
        opClassesLock.readLock().unlock();
    }
}

Had I checked the documentation for ReentrantReadWriteLock, I could have predicted this:

Reentrancy also allows downgrading
from the write lock to a read lock, by
acquiring the write lock, then the
read lock and then releasing the write
lock. However, upgrading from a read
lock to the write lock is not
possible.

Besides just using a single lock instead of a read/write-lock (which won't allow concurrent reads), is there any other way to solve this kind of problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

风筝有风,海豚有海 2024-10-22 20:26:48

使用经过充分测试的解决方案,例如

new MapMaker().weakKeys().makeMap();

Guava 中的解决方案。您甚至可以做类似的事情

new MapMaker().weakKeys()
.concurrencyLevel(16)
.expireAfterAccess(5, TimeUnit.MINUTES) 
.maximumSize(1000)
.makeComputingMap(new Function<Class<?>, ServiceClass>() {
    @Override
    public ServiceClass apply(Class<?> myClass) {
        return new ServiceClass(myClass);
    }
});

来解决您的整个问题,并提供很多调整缓存的可能性。


死锁的原因是在两个线程都持有读锁的情况下获取写锁。与降级锁不同,升级可能会阻塞。您需要先释放读锁。


当您获得写锁时,您应该测试另一个线程是否尚未完成该工作。

Use a well-tested solution like

new MapMaker().weakKeys().makeMap();

from Guava. You can even do things like

new MapMaker().weakKeys()
.concurrencyLevel(16)
.expireAfterAccess(5, TimeUnit.MINUTES) 
.maximumSize(1000)
.makeComputingMap(new Function<Class<?>, ServiceClass>() {
    @Override
    public ServiceClass apply(Class<?> myClass) {
        return new ServiceClass(myClass);
    }
});

which should solve your whole problem and offers a lot of possibilities to tune the caching.


The reason for the deadlock is acquiring the write lock while both thread are holding the read lock. Unlike downgrading the lock, upgrading may block. You'd need to release the read lock first.


When you get the write lock, you should test if another thread didn't do the work yet.

一口甜 2024-10-22 20:26:48

我现在意识到,上面的示例如果有效,无论如何都不会有效,因为这种情况可能会发生

  • 线程 1 看到映射中没有条目并获取写锁
  • 线程 2 看到映射中没有条目并获取写锁写锁(但必须等待)
  • 线程 1 添加该条目并释放写锁
  • 线程 2 现在添加该条目,但它已经存在

所以有两个选项:

  1. 如果该条目不能创建两次(因为副作用)对整个方法使用单个锁。或者,释放读锁,获取写锁,并在计算之前再次检查该条目是否仍然丢失。

  2. 如果该条目可能被创建两次(如果它只是一个缓存),则在获取写锁之前释放读锁。

I realise now that the example above, if it worked, would not be valid anyway, because this could happen

  • Thread 1 sees there is no entry in the map and gets the write lock
  • Thread 2 sees there is no entry in the map and gets the write lock (but has to wait)
  • Thread 1 adds the entry and releases the write lock
  • Thread 2 now adds the entry, but it's already there

So there are two options:

  1. If the entry can't be made twice (because of side effects) use a single lock for the entire method. Or, release the read lock, acquire the write lock, and check again if the entry is still missing before calculating it.

  2. If the entry may be made twice (if it's just a cache) release the read lock before getting the write lock.

柒夜笙歌凉 2024-10-22 20:26:48

我认为在获取写锁之前释放读锁可能会起作用。
但是在释放读锁后的同一时刻,其他线程可能会夺走该锁,从而导致前一个线程处于等待状态。

I think releasing the read lock before acquiring the write one may work.
But the at the exact moment after releasing the read lock, some other threads may take the lock away, which leads to the waiting of the previous thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文