这个问题已经在这里有了答案: > Proper way to try-catch a semaphore 1个
这是大多数在线资源中编写的代码.但这是不正确的,因为考虑了线程阻塞然后被中断的情况.
即使线程尚未获得锁,它仍将释放锁.这是不正确的.那么在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);
}
解决方法:
信号量没有所有权概念.许可证不是真实的东西,只是信号量保持的计数.所以问题是,方法执行后计数正确吗?
如果您被打扰了,您会留下一个或两个可用许可证吗?
api文档中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块,该final进行了一些清理(如果结果证明没有添加任何许可证,则释放许可),在进入该try-block之前调用acquire.
如果在此示例中,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();
}
或考虑acquireUninterruptible().但是考虑一下,如果这些调用没有引发InterruptedException,那么代码的哪一部分可以确保在收到中断请求时代码实际上停止工作?看起来您可能陷入了一个无用的循环,在这种循环中,线程尝试获取,抛出InterruptedException,捕获它并设置中断状态,然后在线程下次尝试获取时重复一遍又一遍.抛出InterruptedException使您可以快速响应取消请求,同时确保在finally块中完成了清除操作.