Java 在同一个try块中锁定多个可重入的TreadWriteLock安全吗?

Java 在同一个try块中锁定多个可重入的TreadWriteLock安全吗?,java,concurrency,reentrantreadwritelock,Java,Concurrency,Reentrantreadwritelock,假设我有两个关键资源,foo和bar。我用一些木棍保护它们 大多数操作只使用foo或bar,但有些操作恰好同时使用这两种语言。现在,当使用单个锁时,您不能只执行以下操作: void foo() { foo.writeLock().lock(); privateWorkOnFoo(); foo.writeLock().unlock(); } 如果抛出异常,您的foo将永远被锁定。而是把它包起来,就像 void foo() { try { foo.writ

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

大多数操作只使用foo或bar,但有些操作恰好同时使用这两种语言。现在,当使用单个锁时,您不能只执行以下操作:

void foo() {
   foo.writeLock().lock();
   privateWorkOnFoo();
   foo.writeLock().unlock();
}
如果抛出异常,您的foo将永远被锁定。而是把它包起来,就像

void foo() {
    try {
        foo.writeLock().lock();
        privateWorkOnFoo();
    } finally { foo.writeLock().unlock(); }
}
但如果我两个都要做呢?把它们放在一个街区安全吗

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

选择2
我不可能是第一个对这件事进行艰难调查的人。。。我知道选项2有“防弹”功能,但也需要大量的维护。选项1可以接受吗?

选项1可以。它被称为双锁变体。如果查看LinkedBlockingQueue操作(如remove),它会锁定putLock和takeLock。下面是JDK的一个示例:

  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中抛出异常,第二个锁(
foo
)将不会解锁:解锁不在finally块中


另外,在操作两个锁时要非常小心,因为如果一个线程锁定
foo
然后锁定
bar
,另一个线程锁定
bar
然后锁定
foo

,则死锁的可能性很大。根据Lock API,Lock()和unlock()方法都可能引发异常。因此版本1是不正确的,因为第二次解锁可能永远不会被调用。版本2也不正确,您不应该在try块中调用lock(),因为如果lock.lock()引发异常,则它未被锁定,您不应该尝试解锁它

正确的版本应该是这样的

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

如果
magic()
在选项2中抛出运行时异常,那么foo将不会被解锁。@Cory这是我的转录错误。选项2应该有第二个finally块。在我读到的javadoc中,lock()不会引发任何异常,unlock()只会引发一个异常,因为你不是锁的所有者,这在给定代码中是不可能的。我完全同意。在API中没有这样的例外,并且查看Doug Lea关于锁的源代码。即使写锁也不会抛出。但lock()API就是这么说的:锁实现可能能够检测锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能抛出(未检查的)异常。此外,这个锁使用示例也来自API:l.lock();请尝试{//访问受此锁保护的资源}最后{l.unlock();}然后我们将不会读取相同的API文档。这是我的:。无论如何,如果程序员不正确地使用锁,这是一个bug,正确地解锁它不会使bug消失,程序也不会正常工作。我同意你的代码是正确的。但是选项1也是。那是一个转录错误。哎呀。我已经记录了获取多个锁的顺序,这是一个很好的例子。在这种情况下,他们需要堵住队列的两端,以防止有人偷偷地进入额外的元素。
try {
    foo.writeLock().lock();
    try {
        bar.writeLock().lock();
        magic();
    } finally { 
      bar.writeLock().unlock();
    }
    
} finally { 
    foo.writeLock().unlock();
}
  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();
    }
    foo.writeLock().lock();
    try {
        bar.writeLock().lock();
        try {
            magic();
        } finally {
            bar.writeLock().unlock();
        }
    } finally { 
        foo.writeLock().unlock();
    }