在Java中发布Semaphore对象的正确方法是什么?

时间:2017-04-09 01:39:08

标签: 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);

}

3 个答案:

答案 0 :(得分:3)

信号量没有所有权概念。许可证不是真实的东西,它只是信号量保持的计数。所以问题是,方法执行后是否正好计数? 如果你被打断了,你是否留下了一个或两个可用的许可证?

api docs中Oracle网站上的信号量示例没有任何finally块,在很多情况下它们并不相关。

如果您正在使用此信号量来实现互斥锁且它只有一个许可证,我希望它应该有一个try-finally块,就像使用锁(来自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块之上,然后在try块中使用它,并在finally块中关闭/释放/清理。这适用于IO,JDBC资源等等。您可以尝试避免这种情况,然后将获取放在try块中 在清理之前检查null。您还可以尝试在finally块中执行太多操作,无法在关闭时捕获异常,并创建资源泄漏。最好尽量减少finally块中的代码量。

正确的例子是http://jcip.net/listings/BoundedHashSet.java(来自Java Concurrency一书) 在实践中):

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块,它执行了一些清理(如果结果没有添加到集合中,则释放许可),其中在输入try-block之前调用acquire。

如果在此示例中,获取调用已移至try块内,则无关紧要。我认为将调用置于try块之上是更好的样式,但在此示例中它不会影响正确性,因为它使用标志来决定是否释放许可。

我会在jcip示例中使用一个标志,在获取后将其设置为true,并且仅在设置了标志时才释放。这样你就可以将获取放在try块中。

    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块中完成清理。

答案 1 :(得分:1)

只需添加一个标志,指示是否已获得锁定:

boolean acquired = false;
try {
    lock.acquire();
    acquired = true;
    // critical section

} catch (InterruptedException e) {

     // do anything

} finally {
   if (acquired) {
       lock.release()
   }
}

另一种解决方案是使用Semaphore.acquireUninterruptibly()

答案 2 :(得分:0)

  

即使没有获得锁定,线程仍将释放锁定。这是不正确的。

不,不是。这里没有什么不妥。来自Javadoc

  

不要求发布许可证的线程必须通过调用acquire().获得该许可证。通过应用程序中的编程约定建立信号量的正确使用。

这里没有问题需要解决。