Java 为什么必须 wait() 总是在同步块中

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2779484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:46:21  来源:igfitidea点击:

Why must wait() always be in synchronized block

javamultithreadingconcurrencywait

提问by diy

We all know that in order to invoke Object.wait(), this call must be placed in synchronized block, otherwise an IllegalMonitorStateExceptionis thrown. But what's the reason for making this restriction?I know that wait()releases the monitor, but why do we need to explicitly acquire the monitor by making particular block synchronized and then release the monitor by calling wait()?

我们都知道,为了调用Object.wait(),这个调用必须放在synchronized块中,否则IllegalMonitorStateException会抛出an 。但是做出这个限制的原因是什么?我知道wait()释放监视器,但是为什么我们需要通过使特定块同步来显式获取监视器,然后通过调用释放监视器wait()

What is the potential damage if it was possible to invoke wait()outside a synchronized block, retaining it's semantics - suspending the caller thread?

如果可以wait()在同步块之外调用并保留其语义 - 挂起调用者线程,那么潜在的损害是什么?

采纳答案by Michael Borgwardt

A wait()only makes sense when there is also a notify(), so it's always about communication between threads, and that needs synchronization to work correctly. One could argue that this should be implicit, but that would not really help, for the following reason:

Await()仅在还有a时才有意义notify(),因此它始终与线程之间的通信有关,并且需要同步才能正常工作。人们可能会争辩说这应该是隐含的,但这并没有真正的帮助,原因如下:

Semantically, you never just wait(). You need some condition to be satsified, and if it is not, you wait until it is. So what you really do is

从语义上讲,你永远不会只是wait(). 你需要满足一些条件,如果不是,你就等到它满足。所以你真正做的是

if(!condition){
    wait();
}

But the condition is being set by a separate thread, so in order to have this work correctly you need synchronization.

但是条件是由一个单独的线程设置的,所以为了使这项工作正常工作,您需要同步。

A couple more things wrong with it, where just because your thread quit waiting doesn't mean the condition you are looking for is true:

它还有一些问题,仅仅因为您的线程退出等待并不意味着您正在寻找的条件为真:

  • You can get spurious wakeups (meaning that a thread can wake up from waiting without ever having received a notification), or

  • The condition can get set, but a third thread makes the condition false again by the time the waiting thread wakes up (and reacquires the monitor).

  • 你可以得到虚假的唤醒(意味着一个线程可以在没有收到通知的情况下从等待中唤醒),或者

  • 可以设置条件,但第三个线程在等待线程唤醒(并重新获取监视器)时再次使条件为假。

To deal with these cases what you really need is alwayssome variation of this:

为了处理这些情况,您真正需要的总是一些变化:

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}

Better yet, don't mess with the synchronization primitives at all and work with the abstractions offered in the java.util.concurrentpackages.

更好的是,根本不要弄乱同步原语,而是使用java.util.concurrent包中提供的抽象。

回答by aioobe

What is the potential damage if it was possible to invoke wait()outside a synchronized block, retaining it's semantics - suspending the caller thread?

如果可以wait()在同步块之外调用并保留其语义 - 挂起调用者线程,那么潜在的损害是什么?

Let's illustrate what issues we would run into if wait()could be called outside of a synchronized block with a concrete example.

让我们wait()用一个具体的例子来说明如果可以在同步块之外调用我们会遇到什么问题。

Suppose we were to implement a blocking queue (I know, there is already one in the API :)

假设我们要实现一个阻塞队列(我知道,API 中已经有一个:)

A first attempt (without synchronization) could look something along the lines below

第一次尝试(没有同步)可能看起来像下面的东西

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}

This is what could potentially happen:

这是可能发生的事情:

  1. A consumer thread calls take()and sees that the buffer.isEmpty().

  2. Before the consumer thread goes on to call wait(), a producer thread comes along and invokes a full give(), that is, buffer.add(data); notify();

  3. The consumer thread will now call wait()(and missthe notify()that was just called).

  4. If unlucky, the producer thread won't produce more give()as a result of the fact that the consumer thread never wakes up, and we have a dead-lock.

  1. 消费者线程调用take()并看到buffer.isEmpty().

  2. 在消费者线程继续调用之前wait(),生产者线程出现并调用一个完整的give(),即,buffer.add(data); notify();

  3. 消费者线程现在将调用wait()(和错过notify()刚叫)。

  4. 如果不走运,give()由于消费者线程永远不会唤醒,生产者线程将不会产生更多的结果,并且我们有一个死锁。

Once you understand the issue, the solution is obvious: Use synchronizedto make sure notifyis never called between isEmptyand wait.

一旦你理解了这个问题,解决方案是显而易见的:用于synchronized确保notify永远不会在isEmpty和之间调用wait

Without going into details: This synchronization issue is universal. As Michael Borgwardt points out, wait/notify is all about communication between threads, so you'll always end up with a race condition similar to the one described above. This is why the "only wait inside synchronized" rule is enforced.

不详述:这个同步问题是普遍的。正如 Michael Borgwardt 指出的那样,wait/notify 完全是关于线程之间的通信,因此您最终会遇到与上述类似的竞争条件。这就是强制执行“仅在同步内等待”规则的原因。



A paragraph from the link posted by @Williesummarizes it quite well:

@Willie 发布链接中的一段总结得很好:

You need an absolute guarantee that the waiter and the notifier agree about the state of the predicate. The waiter checks the state of the predicate at some point slightly BEFORE it goes to sleep, but it depends for correctness on the predicate being true WHEN it goes to sleep. There's a period of vulnerability between those two events, which can break the program.

您需要绝对保证服务员和通知者就谓词的状态达成一致。服务员会在谓词进入睡眠之前的某个时刻检查它的状态,但它的正确性取决于谓词在它进入睡眠时为真。这两个事件之间存在一段脆弱性,这可能会破坏程序。

The predicate that the producer and consumer need to agree upon is in the above example buffer.isEmpty(). And the agreement is resolved by ensuring that the wait and notify are performed in synchronizedblocks.

生产者和消费者需要达成一致的谓词在上面的例子中buffer.isEmpty()。并且通过确保等待和通知在synchronized块中执行来解决协议。



This post has been rewritten as an article here: Java: Why wait must be called in a synchronized block

这篇文章已经在这里重写为一篇文章:Java:为什么必须在同步块中调用等待

回答by Rollerball

directly from thisjava oracle tutorial:

直接来自这个java oracle教程:

When a thread invokes d.wait, it must own the intrinsic lock for d — otherwise an error is thrown. Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock.

当一个线程调用 d.wait 时,它必须拥有 d 的内在锁——否则会抛出错误。在同步方法中调用等待是获取内在锁的简单方法。

回答by ashoka.devanampriya

@Rollerball is right. The wait()is called, so that the thread can wait for some condition to occur when this wait()call happens, the thread is forced to give up its lock.
To give up something, you need to own it first. Thread needs to own the lock first. Hence the need to call it inside a synchronizedmethod/block.

@Rollerball 是对的。将wait()被调用,从而使线程可以等待某些条件时,这种情况发生wait()调用发生时,线程被迫放弃其锁。
要放弃某样东西,首先要拥有它。线程首先需要拥有锁。因此需要在synchronized方法/块中调用它。

Yes, I do agree with all the above answers regarding the potential damages/inconsistencies if you did not check the condition within synchronizedmethod/block. However as @shrini1000 has pointed out, just calling wait()within synchronized block will not avert this inconsistency from happening.

是的,如果您没有检查synchronized方法/块中的条件,我同意上述所有关于潜在损害/不一致的答案。然而,正如@shrini1000 指出的那样,仅wait()在同步块内调用不会避免这种不一致的发生。

Here is a nice read..

这是一个很好的阅读..

回答by Marcus

This basically has to do with the hardware architecture (i.e. RAMand caches).

这基本上与硬件架构有关(即RAM缓存)。

If you don't use synchronizedtogether with wait()or notify(), another thread couldenter the same block instead of waiting for the monitor to enter it. Moreover, when e.g. accessing an array without a synchronized block, another thread may not see the changement to it...actually another thread will notsee any changements to it whenit already has a copy of the array in the x-level cache (a.k.a. 1st/2nd/3rd-level caches) of the thread handling CPU core.

如果不synchronizedwait()或一起使用notify(),另一个线程可以进入同一个块而不是等待监视器进入它。此外,例如,当访问一个没有同步块的数组时,另一个线程可能看不到它的变化……实际上,当另一个线程在 x 级缓存中已经有一个数组副本时,不会看到任何变化(又名 1st/2nd/3rd-level caches)的线程处理 CPU 核心。

But synchronized blocks are only one side of the medal: If you actually access an object within a synchronized context from a non-synchronized context, the object still won't be synchronized even within a synchronized block, because it holds an own copy of the object in its cache. I wrote about this issues here: https://stackoverflow.com/a/21462631and When a lock holds a non-final object, can the object's reference still be changed by another thread?

但是同步块只是奖牌的一方面:如果您实际上从非同步上下文访问同步上下文中的对象,即使在同步块中,该对象仍然不会同步,因为它拥有自己的副本对象在其缓存中。我在这里写过这个问题:https: //stackoverflow.com/a/21462631当锁持有一个非最终对象时,对象的引用仍然可以被另一个线程更改吗?

Furthermore, I'm convinced that the x-level caches are responsible for most non-reproducible runtime errors. That's because the developers usually don't learn the low-level stuff, like how CPU's work or how the memory hierarchy affects the running of applications: http://en.wikipedia.org/wiki/Memory_hierarchy

此外,我确信 x 级缓存是造成大多数不可重现的运行时错误的原因。那是因为开发人员通常不会学习底层的东西,比如 CPU 的工作方式或内存层次结构如何影响应用程序的运行:http: //en.wikipedia.org/wiki/Memory_hierarchy

It remains a riddle why programming classes don't start with memory hierarchy and CPU architecture first. "Hello world" won't help here. ;)

为什么编程类不首先从内存层次结构和 CPU 架构开始,这仍然是一个谜。“Hello world”在这里无济于事。;)

回答by Shay.G

The problem it may cause if you do notsynchronize before wait()is as follows:

之前同步可能导致的问题wait()如下:

  1. If the 1st thread goes into makeChangeOnX()and checks the while condition, and it is true(x.metCondition()returns false, means x.conditionis false) so it will get inside it. Then just before the wait()method, another thread goes to setConditionToTrue()and sets the x.conditionto trueand notifyAll().
  2. Then only after that, the 1st thread will enter his wait()method (not affected by the notifyAll()that happened few moments before). In this case, the 1st thread will stay waiting for another thread to perform setConditionToTrue(), but that might not happen again.
  1. 如果第一个线程进入makeChangeOnX()并检查 while 条件,并且它是truex.metCondition()返回false,意味着x.conditionfalse),那么它将进入其中。然后就在该wait()方法之前,另一个线程转到setConditionToTrue()并设置x.conditiontotruenotifyAll()
  2. 然后只有在那之后,第一个线程才会进入他的wait()方法(不受notifyAll()之前发生的影响)。在这种情况下,第一个线程将等待另一个线程执行setConditionToTrue(),但这可能不会再次发生。

But if you put synchronizedbefore the methods that change the object state, this will not happen.

但是如果你把synchronized改变对象状态的方法放在前面,这不会发生。

class A {

    private Object X;

    makeChangeOnX(){
        while (! x.getCondition()){
            wait();
            }
        // Do the change
    }

    setConditionToTrue(){
        x.condition = true; 
        notifyAll();

    }
    setConditionToFalse(){
        x.condition = false;
        notifyAll();
    }
    bool getCondition(){
        return x.condition;
    }
}

回答by lakshman reddy

When you call notify() from an object t, java notifies a particular t.wait() method. But, how does java search and notify a particular wait method.

当您从对象 t 调用 notify() 时,java 会通知特定的 t.wait() 方法。但是,java 如何搜索并通知特定的等待方法。

java only looks into the synchronized block of code which was locked by object t. java cannot search the whole code to notify a particular t.wait().

java 只查看被对象 t 锁定的同步代码块。java 无法搜索整个代码以通知特定的 t.wait()。

回答by Aavesh Yadav

We all know that wait(), notify() and notifyAll() methods are used for inter-threaded communications. To get rid of missed signal and spurious wake up problems, waiting thread always waits on some conditions. e.g.-

我们都知道wait()、notify()和notifyAll()方法用于线程间通信。为了摆脱信号丢失和虚假唤醒问题,等待线程总是在某些条件下等待。例如-

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

Then notifying thread sets wasNotified variable to true and notify.

然后通知线程将 wasNotified 变量设置为 true 并通知。

Every thread has their local cache so all the changes first get written there and then promoted to main memory gradually.

每个线程都有自己的本地缓存,因此所有更改首先写入那里,然后逐渐提升到主内存。

Had these methods not invoked within synchronized block, the wasNotified variable would not be flushed into main memory and would be there in thread's local cache so the waiting thread will keep waiting for the signal although it was reset by notifying thread.

如果这些方法没有在 synchronized 块中调用,则 wasNotified 变量将不会被刷新到主内存中,而是存在于线程的本地缓存中,因此等待线程将继续等待信号,尽管它已被通知线程重置。

To fix these types of problems, these methods are always invoked inside synchronized block which assures that when synchronized block starts then everything will be read from main memory and will be flushed into main memory before exiting the synchronized block.

为了解决这些类型的问题,这些方法总是在同步块内调用,以确保当同步块启动时,所有内容都将从主内存中读取,并在退出同步块之前刷新到主内存中。

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}

Thanks, hope it clarifies.

谢谢,希望它澄清。

回答by Arun Raaj

as per docs:

根据文档:

The current thread must own this object's monitor. The thread releases ownership of this monitor.

当前线程必须拥有此对象的监视器。该线程释放此监视器的所有权。

wait()method simply means it releases the lock on the object. So the object will be locked only within the synchronized block/method. If thread is outside the sync block means it's not locked, if it's not locked then what would you release on the object?

wait()方法只是意味着它释放了对象上的锁。所以对象只会在同步块/方法中被锁定。如果线程在同步块之外意味着它没有被锁定,如果它没有被锁定那么你会在对象上释放什么?