java - 在 Java 中释放信号量对象的正确方法是什么?

标签 java multithreading concurrency semaphore

<分区>

这是网上大部分资源写的代码。但这是不正确的,因为考虑一个线程阻塞然后被中断的场景。 即使线程没有获得锁,它仍然会释放锁。这是不正确的。那么在java中释放一个信号量的正确实现是怎样的呢?

Semaphore lock = new Semaphore(1);

  add(){
    try {
        lock.acquire();

        // critical section

    } catch (InterruptedException e) {

          Thread.currentThread().interrupt();

    }finally{
       lock.release()
    }
}

我认为这是正确的解决方案:-。是吗?

try {
    lock.acquire();

    try {
        // do some stuff here
    } finally {

        lock.release();

    }
} catch(InterruptedException ie) {

    Thread.currentThread().interrupt();
    throw new RuntimeException(ie);

}

最佳答案

信号量没有所有权的概念。许可证不是真实的东西,它只是信号量保留的计数。那么问题来了,这个方法执行完之后是不是就计数了? 如果你被打扰了,你会在一两个可用许可的情况下离开这个方法吗?

Oracle 站点上的 api 文档中的信号量示例没有任何 finally block ,在很多情况下它们是不相关的。

如果您使用此信号量来实现互斥量并且它只有一个许可,我希望它应该有一个 try-finally block ,就像使用锁一样(来自 the ReentrantLock api doc):

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

如果代码吃掉 InterruptedException 并让线程继续执行,那么方法末尾的计数是否正确就变得很重要,它可能会阻止其他调用获得许可。

每当我得到一些东西、使用它并释放它时,我使用的一般模式是将它放在 try block 之上,然后在 try block 中使用它,并在 finally block 中关闭/释放/清理。这适用于 IO、JDBC 资源等。你可以尽量避免这种情况,将获取放在try block 中,然后 在清理之前检查是否为 null。您还可以尝试在 finally block 中做太多事情,在关闭时无法捕获异常,并造成资源泄漏。最好尽量减少 finally block 中的代码量。

一个正确的例子在 http://jcip.net/listings/BoundedHashSet.java (摘自《Java 并发》一书 在实践中):

public boolean add(T o) throws InterruptedException {
    sem.acquire();
    boolean wasAdded = false;
    try {
        wasAdded = set.add(o);
        return wasAdded;
    } finally {
        if (!wasAdded)
            sem.release();
    }
}

这显示了一个带有 finally 的 try block ,它执行一些清理(如果结果没有向集合中添加任何内容,则释放许可),其中在输入 try block 之前调用 acquire。

如果在此示例中将 acquire 调用移至 try block 内,则没有关系。我认为将调用放在 try block 之上是更好的风格,但在这个例子中它不会影响正确性,因为它使用标志来决定是否释放许可。

我会像 jcip 示例中那样使用一个标志,在获取后将其设置为 true,并且只有在设置了标志时才释放。这样您就可以将 acquire 放在 try block 中。

    boolean wasAcquired = false;
    try {      
         sem.acquire();
        wasAcquired = true;
        // crit sect
     } catch (InterruptedException e) {
        Thread.currentThread.interrupt();
    } finally {
        if (wasAcquired)
            sem.release();
    }

或者考虑acquireUninterruptibly()。但是想一想,如果这些调用没有抛出 InterruptedException,您的代码的哪一部分可以确保代码在收到中断请求时真正​​停止工作?看起来您可能会陷入一个非生产性循环,其中线程尝试获取、抛出 InterruptedException、捕获它并设置中断状态,然后在下一次线程尝试获取时再次执行相同的操作,一遍又一遍。抛出 InterruptedException 可让您快速响应取消请求,同时确保在 finally block 中完成清理。

关于java - 在 Java 中释放信号量对象的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43302147/

相关文章:

java - GlassFish 中的 "java.version"值比控制台上的值旧

c - Erlang c-nodes 随机崩溃,并出现双重释放、内存损坏 malloc 消息

c++ - 从 C++ 到 Pro *C 的控制转换期间的段错误

c++ - 如何使异步并行程序代码易于管理(例如在 C++ 中)

java - 高效的并发树

ruby - 并发可以提高性能吗?

java - 如何使用 LIKE 返回所有内容,包括 Oracle 中的空值?

java - 是否可以针对 XML、DOM 或其他原始结构使用 Mule MEL 表达式?

java - 使用 handler/runnable 延迟方法的重新提交

java - AtomicReference<Integer> 与 AtomicInteger 之间有什么区别?