2011-02-03 26 views
2

我在下面的代码死锁情况:升级Java的读锁写锁缓存在地图

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

Guava使用以及测试溶液类似

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

。你甚至可以做像

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

这应该解决你的整个问题,并提供了很多可能性来调整缓存。


死锁的原因是在两个线程持有读锁的同时获取写锁。与降级锁定不同,升级可能会阻止。你需要首先释放读锁。


当你得到写入锁定时,你应该测试另一个线程是否还没有完成这项工作。

+0

是的,那正是我需要的。我将使用它并查看源代码以了解其工作原理。 – 2011-02-03 19:34:52

0

我现在认识到,上面的例子,如果它的工作,将不会是有效的,无论如何,因为这可能发生

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

所以有两种选择:

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

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

0

我认为在获取写入之前释放读锁可能会起作用。 但是在释放读锁之后的确切时刻,其他一些线程可能会将锁取走,导致前一个线程的等待。