【问题标题】:Why do we need to synchronize on the same object for notify() to work为什么我们需要在同一个对象上同步才能使 notify() 工作
【发布时间】:2013-08-07 05:26:13
【问题描述】:

我收到了java.lang.IllegalMonitorStateException。我提到了this 问题,它解决了我的问题。第一个答案是

To be able to call notify() you need to synchronize on the same object.

synchronized (someObject) {
    someObject.wait();
}

/* different thread / object */
synchronized (someObject) {
    someObject.notify();
}  

我的问题是为什么我们需要在同一个对象上同步广告它是如何工作的?

就我的理解而言,当我们说

synchronized (someObject) {
    someObject.wait();
}

我们在对象 someObject 上获得了一个锁,然后我们在它上面调用 wait()。 现在另一个线程如何锁定同一个对象来调用 notify() 呢?我错过了什么?

【问题讨论】:

  • 你为什么用粗体字提到你的问题? (像这样)

标签: java synchronization wait notify


【解决方案1】:

为什么notify 也需要锁?

想象一下这个场景:

synchronized(x){
        while(x.count < 4) {
          x.wait();
          //...
        }
}

现在想象一下在其他地方没有任何锁定的notify

//...
println(x.count); // print 3
x.count++;
if(count == 4) 
  x.notify()
//...

乍一看,整个声音总是按预期工作。
但是,想象一下这种竞争条件:

//Thread1 enters here
synchronized(x){
     while(x.count < 4) {
         //condition is judged true and thread1 is about to wait 
         //..but..ohh!! Thread2 is prioritized just now !
         //Thread2, acting on notify block side, notices that with its current count incrementation, 
         //count increases to 4 and therefore a notify is sent.... 
         //but...but x is expected to wait now !!! for nothing maybe indefinitely !
       x.wait();
       //maybe block here indefinitely waiting for a notify that already occurred!
     }
}

如果我们有办法告诉notify 方:

线程 1:“嗯..notify,你很可爱,但我刚刚开始评估我的状况 (x.count &lt; 4) 是否正确,所以请......不要傻傻地发送你预期的通知刚刚(在我将状态置于等待之前),否则,等待已经过去的事情会很可笑”

Thread2:“好的,好的...我将锁定我的逻辑以保持一致,以便我在您的等待调用释放我们的共享锁之后发送我的通知,因此您将收到此通知,允许退出您的等待状态;)"

因此,始终在notify 侧,在等待持有的同一个对象上放置一个锁,以避免这种情况并让关系始终保持一致。

=> 导致notify 的逻辑和导致wait 的逻辑不应重叠。

【讨论】:

  • @Mik378 很好的解释很容易理解。谢谢
  • 我认为没有必要持有锁。在您的示例中,条件变量 (x) 应在同步后修改,这就是您所需要的。我认为您可以在更改条件变量(x)后释放锁(监视器),然后通知。实际上在 C++ 中,它甚至鼓励在通知之前释放锁。见cppreference
【解决方案2】:

根据 Object#wait() 的 javadoc

当前线程必须拥有该对象的监视器。线程释放 此监视器的所有权并等待直到另一个线程通知 在此对象的监视器上等待唤醒的线程要么通过 调用 notify 方法或 notifyAll 方法。然后线程 等待直到它可以重新获得监视器的所有权并恢复 执行。

要使用等待/通知线程必须有锁,否则 IllegalMonitorStateException 被抛出

抛出:IllegalMonitorStateException - 如果当前线程不是 对象监视器的所有者。

为什么

所以, wait() 使当前线程释放

notify() 向其他等待线程发出信号,然后尝试获取锁。

为了做到这点,当前线程必须拥有锁。这是有道理的!

编辑

  • 为什么线程调用 wait() 必须持有锁现在很明显了。

  • 但是为什么线程调用 notify() 必须持有锁呢?好吧,一方面,证明它的authenticity。否则任何线程都可以继续触发false notifications,而waiting 线程将继续被中断。 谢天谢地,事实并非如此。

【讨论】:

  • 但是为什么一定要持有锁才能调用notify呢?说“因为规范是这样说的”是对的,但并没有真正回答问题。
  • 你写“当前线程必须有锁”:为什么它必须有锁才能向其他线程发出信号?他们不在乎谁握着锁;无论如何,他们都会尝试获取它。
  • How can you release a resource which you don't have in the first place? 会有帮助吗?
  • 这就是为什么你必须持有锁才能调用等待。但我们正在讨论通知。
【解决方案3】:

wait/notify 通常用于等待某个其他线程完成任务,或者等待某个条件满足。

假设我们有一个名为 objectA 的对象和两个名为 thread1 和 thread2 的线程。
thread1 有一些线程安全任务,因此它使用 synchronized 块获取 objectA 的监视器。

       synchronized (objectA) {
          //here thread1  owns objectA's monitor
       }  

在java中调用wait()意味着释放监视器,以便其他线程可以得到这个监视器并完成它的任务,并且当前线程进入对象A的监视器的某种称为等待状态的状态。

     synchronized(objectA){
        //here thread1 owns objectA's monitor.
        objectA.wait();
        //here thred1 releases monitor of objectA's monitor and goes into waiting state and waits to get objectA's monitor once again to complete its task.
     }

现在 thread2 可以拥有 objectA 的监视器并执行它的任务。

    synchronized(objectA){
        //here thread2 owns objectA's monitor.
        //some task;
     }

一旦任务完成,它就会通知其他处于等待状态的线程它已释放它拥有的对象上的监视器。 请注意,调用 notify() 的线程也应该是对象监视器的所有者

       synchronized(objectA){
        //here thread2 owns objectA's monitor.
        //some task;
        objectA.notify();
       //it signals some other thread that it can wake up from wait,so that other waiting threads can owns objectA's monitor
     }

这里在 objectA 上调用 wait() 并在其他对象上调用 notify()(可以说 objectB)对 thread1 没有用。因为 thread1 正在等待监视器在 objectA 上而不是在其他对象上(比如说 objectB)。

更新 Why obtain monitor to call notify()
要调用 notify() 我们需要获取监视器,因为可以保证尝试在一个对象上调用 notify() 的两个线程不会踩到对方的脚趾(避免竞争条件)。why we need to get lock before notify

【讨论】:

  • 很好的解释,但问题仍然存在,为什么要获取锁/监视器来通知()?
  • 调用notify 不会释放监视器;它向其他线程发出信号,表明它可以从wait 唤醒并尝试获取监视器。此外,从链接到的图表中可以看到,notify 使线程进入 runnable 状态,而不是 terminated 状态。
  • @Joni 非常感谢您的纠正。对不起,我误解了这个概念。我想我现在正确更新了。如果有任何错误,请查看并纠正我
  • @Joni 那么在synchronized块内调用notify()后,会不会释放线程进入sync块时获取的监听器?
  • 没关系得到答案等待立即松开锁,而通知只有在遇到结束括号时才会离开锁
【解决方案4】:

查看 Hotspot JVM 源代码,我发现:notify() 方法修改了对象监视器的等待集。 (对象的等待集是对其调用wait() 的线程集。)如果对等待集的访问未同步,则可能会发生坏事:例如,线程可能会从集合中删除而从未被同步醒来。要求调用线程在调用 notify() 之前拥有监视器可以解决此问题,但也可能存在其他解决方案。

还有其他参数,例如在不持有监视器的情况下调用通知通常意味着程序员错误,但我认为这不足以激发这样的限制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多