在同一个try块中锁定多个ReentrantReadWriteLocks是否安全?

假设我有两个关键资源,foo和bar。 我用一些ReentrantReadWriteLock保护它们

 ReentrantReadWriteLock foo = new RRWL() ... ReentrantReadWriteLock bar = new RRWL() ... 

大多数操作只使用foo OR bar,但其中一些恰巧使用两者。 现在使用单个锁时,你不能只这样做:

 void foo() { foo.writeLock().lock(); privateWorkOnFoo(); foo.writeLock().unlock(); } 

如果抛出exception,你的foo将永远被锁定。 相反,你包装它,就像

 void foo() { try { foo.writeLock().lock(); privateWorkOnFoo(); } finally { foo.writeLock().unlock(); } } 

但是,如果我需要同时工作呢? 将它们放在一个块中是否安全?

选项1

 try { foo.writeLock().lock(); bar.writeLock().lock(); magic(); } finally { bar.writeLock().unlock(); foo.writeLock().unlock(); } 

或者是否有必要为每个锁提供自己的块:

选项2

 try { foo.writeLock().lock(); try { bar.writeLock().lock(); magic(); } finally { bar.writeLock().unlock(); } } finally { foo.writeLock().unlock(); } 

我不可能是第一个很难对此进行调查的人……我知道选项2有“防弹”但它也是一个相当多的维护。 选项1是否可以接受?

选项1没问题。 它被称为两种锁定变体。 如果你看一下LinkedBlockingQueue操作,比如remove,它会锁定putLock以及takeLock。 以下是JDK的function示例:

  public boolean remove(Object o) { if (o == null) return false; fullyLock(); try { // ... } finally { fullyUnlock(); } } /** * Lock to prevent both puts and takes. */ void fullyLock() { putLock.lock(); takeLock.lock(); } /** * Unlock to allow both puts and takes. */ void fullyUnlock() { takeLock.unlock(); putLock.unlock(); } 

选项1实际上比选项2更安全,因为如果在选项2中抛出exception,则第二个锁( foo )将不会被解锁:解锁不在finally块中。

另外,在操作两个锁时要非常小心,因为如果一个线程锁定foo然后bar ,则很有可能发生死锁,另一个线程锁定bar然后foo

根据Lock API,lock()和unlock()方法都可能抛出exception。 因此版本1不正确,因为可能永远不会调用第二个解锁。 版本2也不正确,你不应该在try块中调用lock(),因为如果lock.lock()抛出exception,那么它没有被锁定,你不应该尝试解锁它。

正确的版本应该是这样的

  foo.writeLock().lock(); try { bar.writeLock().lock(); try { magic(); } finally { bar.writeLock().unlock(); } } finally { foo.writeLock().unlock(); }