在同一个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(); }