Reentrantlock - 为什么我们需要多次获取锁?

Reentrantlock - Why do we need to acquire a lock multiple times?

我最近一直在学习 java 中的多线程概念。我有一些疑问,通过在 Whosebug 上查找相关线程并没有解决。我找不到以下问题的满意答案:

  1. wait() 方法使线程等待直到获得锁。而 wait(long timeout) 方法使线程等待 'timeout' 没有。毫秒,如果它仍然没有获得锁定,则返回到可运行状态。但是要真正进入 运行 状态,它需要锁。那么 wait(long timeout) 方法有什么意义呢?然而,当线程处于等待状态时,它会释放它获得的锁。所以区别甚至不在于它获得的资源。如果线程处于等待状态或可运行状态,这有什么区别? wait(long timeout) 比 wait() 方法有什么优势?

  2. synchonized 关键字或块提供对调用方法或块的对象的锁定。它会导致另一个试图获取同一实例上的锁的线程等待。但是在ReentrantLock的情况下,是在哪个对象上获取锁呢?试图获取其锁的线程等待?

  3. ReentrantLock如何避免死锁?假设有两种方法 m1 和 m2。两者都需要获取锁。 m1 正在调用 m2,m2 正在调用 m1。在这种情况下,我们如何使用 ReentrantLock 来避免死锁呢?也许我们可以使用 tryLock() 并为未能获取锁的线程提供备用操作。但是可能的替代操作是什么?如果线程必须需要锁才能工作怎么办?

  4. 我发现使用ReentrantLock我们可以多次获取锁。但是为什么我们要多次获取锁呢?我已经阅读了这方面的理论答案,但无法真正理解。如果您能用清晰的示例代码进行演示,将会很有帮助。

Why do we need to acquire a lock multiple times?

显然,您不需要。但应用程序“意外”执行此操作并不罕见。例如:

  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 也会发生同样的情况。


问题一

But to actually get to running state, it needs the lock however. So what's the point of wait(long timeout) method?

这大约是 Object::waitReentrantLock API 不支持这个。 (注意:您可以在 ReentrantLock 对象上使用 waitnotify,但前提是您将其视为原始锁。这不是个好主意!)

wait表示等待通知,timeout表示调用方准备等待通知的时间。正如 javadoc 所说:

"Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed."

对于 wait()wait(timeout),由调用者检查它期望被“通知”的条件是否实际满足。 (请参阅有关“虚假唤醒”的注释......和示例代码。)

What's the advantage of wait(long timeout) over wait() method?

简单地说,它让您可以选择仅在有限的时间内等待通知。如果这没有用,请不要使用它。


问题二

But in the case of ReentrantLock, on which object is the lock acquired?

严格来说,就是锁本身。锁的实际含义取决于您如何编写 类。但这与原始互斥锁完全相同。

锁定 Java 不会阻止某些行为不当的代码在不持有锁的情况下访问和/或更新某些共享状态。正确的做法取决于程序员。

The threads trying to acquire whose lock are made to wait?

是的。


问题三

How does a ReentrantLock avoid deadlock?

一般情况下不会。

在可重入锁定的情况下(即在线程试图获取锁 A 的同时持有锁 A 的情况),ReentrantLock 实现注意到持有锁的线程是获取锁的线程.计数器递增,因此实现知道必须释放锁两次

How can we avoid deadlock in this situation using ReentrantLock? May be we can use tryLock() and provide an alternate operations for the thread which fails to acquire the lock.

这是一种方法。

But what could be the possible alternate operations?

  1. 确保所有线程以相同顺序获取锁。 (当线程试图以不同的顺序获取两个或更多线程时会发生死锁。)

  2. 如果tryLock在持有不同的锁时失败,释放锁,稍等片刻,然后重试。

What if the thread must need the lock to work?

然后你设计逻辑,避免死锁;请参阅上面的备选方案!


问题 4.

But why do we have to acquire lock several times?

如上所述,您通常不会。但是 ReentrantLock 的要点是您不必担心最终两次获得锁的情况......无论出于何种原因。