升级Java读锁以在Map中写入用于缓存的锁

时间:2011-02-03 12:29:54

标签: java concurrency synchronization locking deadlock

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

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的文档,我本可以预测到这一点:

  

重入也允许降级   从写锁定到读锁定   获取写锁定,然后获取   读锁定然后释放写入   锁。 然而,从阅读升级   锁定到写锁定不是   可能的。

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

3 个答案:

答案 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)

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

  • 线程1看到地图中没有条目并获得写锁定
  • 线程2看到地图中没有条目并获得写锁定(但必须等待)
  • 线程1添加条目并释放写锁定
  • 线程2现在添加了条目,但它已经存在

所以有两种选择:

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

  2. 如果输入可能两次(如果它只是一个缓存),则在获得写锁定之前释放读锁定。

答案 2 :(得分:0)

我认为在获取写入锁之前释放读锁可能有效。 但是在释放读锁定的确切时刻,一些其他线程可能会取消锁定,这导致前一个线程的等待。