我在下面的代码中遇到了死锁情况:
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
的文档,我本可以预测到这一点:
重入也允许降级 从写锁定到读锁定 获取写锁定,然后获取 读锁定然后释放写入 锁。 然而,从阅读升级 锁定到写锁定不是 可能的。
除了使用单个锁而不是读/写锁(不允许并发读取)之外,还有其他方法可以解决这类问题吗?
答案 0 :(得分:3)
使用经过充分测试的解决方案,如
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);
}
});
它可以解决你的整个问题,并提供很多调整缓存的可能性。
死锁的原因是在两个线程都持有读锁定时获取写锁定。与降级锁定不同,升级可能会阻止。您需要先释放读锁定。
当你获得写锁定时,你应该测试另一个线程是否还没有完成这项工作。
答案 1 :(得分:0)
我现在意识到上面的例子,如果有效,无论如何都不会有效,因为这可能发生
所以有两种选择:
如果输入不能两次(由于副作用),请对整个方法使用单个锁。或者,释放读锁定,获取写锁定,并在计算之前再次检查该条目是否仍然缺失。
如果输入可能两次(如果它只是一个缓存),则在获得写锁定之前释放读锁定。
答案 2 :(得分:0)
我认为在获取写入锁之前释放读锁可能有效。 但是在释放读锁定的确切时刻,一些其他线程可能会取消锁定,这导致前一个线程的等待。