这是网上大部分资源写的代码。但这是不正确的,因为考虑一个线程阻塞然后被中断的场景。
即使线程没有获得锁,它仍然会释放锁。这是不正确的。那么在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 中完成清理。