最近,我一直在学习java中的多线程概念。我有一些疑问,没有通过查找StackOverflow上的相关线程来解决。以下问题我找不到满意的答案:
发布于 2019-08-10 14:56:31
为什么我们需要多次获得锁?
很明显,你不需要。但是,申请“偶然”这样做并不罕见。例如:
public void binaryOperation(Operand op1, Operand op2) {
synchronized (op1) {
synchronized (op2) {
// do something that needs the locks
}
}
}
// now call passing the same object for both operands
Operand op = ...
binaryOperation(op, op);
在本例中,op
对象实际上将被锁定两次。如果原始锁不是可重入的,这可能会失败(或者死锁)。
现在我们可以修复binaryOperation
方法而不是这样做,但是它会使代码变得更加复杂。
同样的场景也可以发生在ReentrantLock
上。
问题1.
但是要真正进入运行状态,它需要锁。那么等待(长超时)方法有什么意义呢?
这是关于Object::wait
的。ReentrantLock
API不支持这一点。(注意:您可以在一个wait
对象上使用notify
和notify
,但前提是您将它作为一个原始锁处理。不是个好主意!)
wait
正在等待通知,而timeout
则表示调用方准备等待通知的时间。正如javadoc所说:
“导致当前线程等待,直到另一个线程为该对象调用
notify()
方法或notifyAll()
方法,或经过指定的时间。”
对于wait()
和wait(timeout)
,应该由调用方来检查它希望得到“通知”的条件是否确实满足。(见关于“虚假唤醒”的注释.以及示例代码。)
与等待()方法相比,等待(长超时)方法的优点是什么?
简单地说,它为您提供了只等待通知的有限时间的选项。如果这没有用,就不要用它。
问题2.
但是在
ReentrantLock
的情况下,锁是在哪个对象上获得的?
严格地说,这是锁本身。锁的实际含义将取决于您如何编写类的代码。但这和原始互斥完全一样。
在Java中锁定并不能阻止一些行为不当的代码在不保持锁的情况下访问和更新某些共享状态。这取决于程序员是否正确地做了它。
试图获取谁的锁的线程是用来等待的?
是。
问题3.
ReentrantLock如何避免死锁?
一般情况下,事实并非如此。
在重入锁定的情况下(即当线程试图在持有锁A时获取锁A时),ReentrantLock
实现注意到持有锁的线程是获取锁的线程。计数器被递增,以便实现知道锁必须释放两次。
在这种情况下,如何使用ReentrantLock避免死锁?也许我们可以使用tryLock(),并为无法获取锁的线程提供一个备用操作。
这是一种方法。
但可能的替代行动是什么呢?
tryLock
在持有另一个锁时失败,请释放锁,稍等,然后再试一次。如果线程必须需要锁才能工作呢?
然后设计逻辑,以避免死锁;请参阅上面的备选方案!
问题4.
但是为什么我们要多次获得锁呢?
正如上面所述,您通常不需要。但是ReentrantLock
的要点是,当您最终获得锁两次时,您不必担心.不管出于什么原因。
发布于 2022-10-30 12:29:04
我认为在某些Oracle文档中很少讨论可重入性的用例(为什么我们要多次获得锁):
此锁允许读取器和写入器以ReentrantLock的样式重新获取读或写锁。在所有由写入线程持有的写锁被释放之前,不能允许不可重入的读取器。 此外,作者可以获得读锁,但反之亦然。在其他应用程序中,在调用或回调在读锁下执行读取的方法时,可重入性可能非常有用。如果读取器试图获取写入锁,则永远不会成功。
回想一下,一个线程不能获得另一个线程拥有的锁。但是线程可以获得它已经拥有的锁。允许线程多次获取相同的锁可启用重入同步。这描述了同步代码直接或间接调用也包含同步代码的方法的情况,而这两组代码使用相同的锁。如果没有重入同步,同步代码将不得不采取许多额外的预防措施,以避免线程导致自身阻塞.。
https://stackoverflow.com/questions/57440851
复制相似问题