这是大多数在线资源编写的代码。但这是不正确的,因为考虑线程阻塞的情况,然后被中断。 即使没有获得锁定,线程仍将释放锁定。这是不正确的。那么在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);
}
答案 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().
获得该许可证。通过应用程序中的编程约定建立信号量的正确使用。
这里没有问题需要解决。