【问题标题】:Reentrantlock - Why do we need to acquire a lock multiple times?Reentrantlock - 为什么我们需要多次获取锁?
【发布时间】:2019-12-17 19:59:08
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: java multithreading concurrency reentrantlock


    【解决方案1】:

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

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

      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。

    但是要真正进入运行状态,它需要锁。那么wait(long timeout)方法的意义何在?

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

    wait 正在等待通知,timeout 表示呼叫者准备等待通知多长时间。正如javadoc 所说:

    “导致当前线程等待,直到另一个线程为此对象调用 notify() 方法或 notifyAll() 方法,或者经过了指定的时间。”

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

    wait(long timeout) 比 wait() 方法有什么优势?

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


    问题 2。

    但是在ReentrantLock的情况下,锁是在哪个对象上获取的呢?

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

    Java 中的锁定不会阻止某些行为不端的代码在不持有锁定的情况下访问和/或更新某些共享状态。由程序员来做正确的事。

    试图获取其锁的线程等待?

    是的。


    问题 3。

    ReentrantLock 如何避免死锁?

    一般情况下不会。

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

    在这种情况下,我们如何使用 ReentrantLock 避免死锁?也许我们可以使用 tryLock() 并为无法获取锁的线程提供替代操作。

    这是一种方法。

    但是可能的替代操作是什么?

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

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

    如果线程必须需要锁才能工作怎么办?

    然后你设计逻辑以避免死锁;请参阅上面的替代方案!


    问题 4。

    但是为什么要多次获取锁呢?

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

    【讨论】:

      猜你喜欢
      • 2013-09-06
      • 2016-01-14
      • 1970-01-01
      • 2019-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 2014-06-18
      相关资源
      最近更新 更多