Java 中的虚假唤醒真的发生了吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1050592/
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-11 22:45:12  来源:igfitidea点击:

Do spurious wakeups in Java actually happen?

javamultithreadinglockingspurious-wakeup

提问by akarnokd

Seeing various locking related question and (almost) always finding the 'loop because of spurious wakeups' terms1I wonder, has anyone experienced such kind of a wakeup (assuming a decent hardware/software environment for example)?

看到各种与锁定相关的问题,并且(几乎)总是发现“由于虚假唤醒而导致的循环”术语1我想知道,有没有人经历过这种唤醒(例如,假设有一个不错的硬件/软件环境)?

I know the term 'spurious' means no apparent reason but what can be the reasons for such kind of an event?

我知道“虚假”一词意味着没有明显的原因,但这种事件的原因是什么?

(1Note: I'm not questioning the looping practice.)

1注意:我不是在质疑循环练习。)

Edit:A helper question (for those who like code samples):

编辑:一个帮助问题(对于那些喜欢代码示例的人):

If I have the following program, and I run it:

如果我有以下程序,并运行它:

public class Spurious {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        lock.lock();
        try {
            try {
                cond.await();
                System.out.println("Spurious wakeup!");
            } catch (InterruptedException ex) {
                System.out.println("Just a regular interrupt.");
            }
        } finally {
            lock.unlock();
        }
    }
}

What can I do to wake this awaitup spuriously without waiting forever for a random event?

我该怎么做才能await在不永远等待随机事件的情况下虚假地唤醒它?

采纳答案by John Kugelman

The Wikipedia article on spurious wakeupshas this tidbit:

维基百科关于虚假唤醒的文章有这样的花絮:

The pthread_cond_wait()function in Linux is implemented using the futexsystem call. Each blocking system call on Linux returns abruptly with EINTRwhen the process receives a signal. ... pthread_cond_wait()can't restart the waiting because it may miss a real wakeup in the little time it was outside the futexsystem call. This race condition can only be avoided by the caller checking for an invariant. A POSIX signal will therefore generate a spurious wakeup.

pthread_cond_wait()Linux 中的函数是使用futex系统调用来实现的。Linux 上的每个阻塞系统调用都会在EINTR进程收到信号时突然返回。...pthread_cond_wait()无法重新开始等待,因为它可能会在futex系统调用之外的短时间内错过真正的唤醒。这种竞争条件只能通过调用者检查不变量来避免。因此,POSIX 信号将产生虚假唤醒。

Summary: If a Linux process is signaled its waiting threads will each enjoy a nice, hot spurious wakeup.

简介: 如果 Linux 进程收到信号,它的每个等待线程都将享受一个不错的、热的虚假唤醒

I buy it. That's an easier pill to swallow than the typically vague "it's for performance" reason often given.

我买它。与通常给出的通常含糊的“这是为了性能”的原因相比,这是一种更容易吞咽的药丸。

回答by oxbow_lakes

Cameron Purdywrote a blog posta while back about being hit by the spurious wakeup problem. So yes, it happens

Cameron Purdy 不久前写了一篇关于被虚假唤醒问题击中的博客文章。所以是的,它发生了

I'm guessing it's in the spec (as a possibility) because of limitations of some of the platforms which Java gets deployed on? although I may be wrong!

我猜它在规范中(作为一种可能性),因为 Java 部署在某些平台上的限制?虽然我可能错了!

回答by Mr.Dirty.Birdy

I have a production system that exhibits this behaviour. A thread waits on a signal that there is a message in the queue. In busy periods, up to 20% of the wakeups are spurious (ie when it wakes there is nothing in the queue). This thread is the only consumer of the messages. It runs on a Linux SLES-10 8-processor box and is built with GCC 4.1.2. The messages come from an external source and are processed asynchronously because there are problems if my system does not read them fast enough.

我有一个表现出这种行为的生产系统。线程等待队列中有消息的信号。在繁忙时段,多达 20% 的唤醒是虚假的(即,当它唤醒时队列中没有任何内容)。该线程是消息的唯一消费者。它在 Linux SLES-10 8 处理器盒上运行,并使用 GCC 4.1.2 构建。消息来自外部源并被异步处理,因为如果我的系统读取它们的速度不够快就会出现问题。

回答by ReneS

Just to add this. Yes it happens and I spent three days searching for the cause of a multi-threading problem on a 24 core machine (JDK 6). 4 of 10 executions experienced that without any pattern. This never happened on 2 core or 8 cores.

只是为了添加这个。是的,它发生了,我花了三天时间在 24 核机器(JDK 6)上寻找多线程问题的原因。10 次执行中有 4 次没有任何模式。这从未发生在 2 核或 8 核上。

Studied some online material and this is not a Java problem but a general rare but expected behavior.

研究了一些在线材料,这不是 Java 问题,而是普遍罕见但预期的行为。

回答by Aniket Thakur

To answer the question in the titile - Yes!it does happen.Though the Wiki articlementions a good deal about spurious wakeups a nice explanation for the same that I came across is as follows -

回答标题中的问题 -是的!它确实发生了。虽然维基文章提到了很多关于虚假唤醒的内容,但对我遇到的相同情况的一个很好的解释如下-

Just think of it... like any code, thread scheduler may experience temporary blackout due to something abnormal happening in underlying hardware / software. Of course, care should be taken for this to happen as rare as possible, but since there's no such thing as 100% robust software it is reasonable to assume this can happen and take care on the graceful recovery in case if scheduler detects this (eg by observing missing heartbeats).

Now, how could scheduler recover, taking into account that during blackout it could miss some signals intended to notify waiting threads? If scheduler does nothing, mentioned "unlucky" threads will just hang, waiting forever - to avoid this, scheduler would simply send a signal to all the waiting threads.

This makes it necessary to establish a "contract" that waiting thread can be notified without a reason. To be precise, there would be a reason - scheduler blackout - but since thread is designed (for a good reason) to be oblivious to scheduler internal implementation details, this reason is likely better to present as "spurious".

试想一下……就像任何代码一样,线程调度程序可能会由于底层硬件/软件发生异常而暂时中断。当然,应该注意这种情况尽可能少发生,但是由于没有 100% 健壮的软件这样的东西,因此假设这种情况可能发生并注意正常恢复,以防调度程序检测到这种情况(例如通过观察丢失的心跳)。

现在,调度程序如何恢复,考虑到在停电期间它可能会错过一些旨在通知等待线程的信号?如果调度程序什么都不做,提到的“不幸”线程将挂起,永远等待 - 为避免这种情况,调度程序将简单地向所有等待线程发送信号。

这使得有必要建立一个“契约”,可以无故通知等待线程。准确地说,会有一个原因 - 调度程序中断 - 但由于线程被设计(有充分的理由)忽略调度程序内部实现细节,这个原因可能更好地表现为“虚假”。

I was reading this answer from Sourceand found it reasonable enough. Also read

我正在从Source阅读这个答案,发现它足够合理。还阅读

Spurious wakeups in Java and how to avoid them.

Java 中的虚假唤醒以及如何避免它们

PS: Above link is to my personal blog that has additional details about spurious wakeups.

PS:上面的链接是我的个人博客,其中包含有关虚假唤醒的更多详细信息。

回答by Gili

https://stackoverflow.com/a/1461956/14731contains an excellent explanation of why you need to guard against of spurious wakeups even if the underlying operating system does not trigger them. It is interesting to note that this explanation applies across multiple programming languages, including Java.

https://stackoverflow.com/a/1461956/14731很好地解释了为什么即使底层操作系统没有触发虚假唤醒也需要防范虚假唤醒。有趣的是,这种解释适用于多种编程语言,包括 Java。

回答by igor.zh

Answering the OP's question

回答 OP 的问题

What can I do to wake this await up spuriously without waiting forever for a random event?

我能做些什么来虚假地唤醒这个等待而无需永远等待随机事件?

, no any spurious wakeupcould wake up this awaiting thread!

没有任何虚假的唤醒可以唤醒这个等待的线程!

Regardless of whether spurious wakeups can or cannot happen on a particular platform, in a case of the OP's snippet it is positively impossiblefor Condition.await()to return and to see the line "Spurious wakeup!" in the output stream.

不管虚假唤醒可以或不可以发生在特定的平台,在OP的的情况下片断是积极不可能Condition.await()返回,看看行“虚假唤醒!” 在输出流中。

Unless you are using very exotic Java Class Library

除非你使用非常奇特的Java 类库

This is because standard, OpenJDK's ReentrantLock's method newCondition()returns the AbstractQueuedSynchronizer's implementation of Conditioninterface, nested ConditionObject(by the way, it is the only implementation of Conditioninterface in this class library), and the ConditionObject's method await()itself checks whether the condition does not holds and no any spurious wakeup could force this method to mistakenly return.

这是因为标准的OpenJDKReentrantLock的方法newCondition()返回AbstractQueuedSynchronizer的实现的Condition接口,嵌套ConditionObject(顺便说一下,它是唯一实现Condition接口在这个类库),和ConditionObject的方法await()本身检查的条件是否不保持并且没有任何虚假唤醒可以强制此方法错误地返回。

By the the way, you could check it yourself as it is pretty easy to emulate spurious wakeup once the AbstractQueuedSynchronizer-based implementation is involved. AbstractQueuedSynchronizeruses low-level LockSupport's parkand unparkmethods, and if you invoke LockSupport.unparkon a thread awaiting on Condition, this action cannot be distinguished from a spurious wakeup.

顺便说一句,您可以自己检查一下,因为一旦AbstractQueuedSynchronizer涉及到基于 - 的实现,就很容易模拟虚假唤醒。 AbstractQueuedSynchronizer使用低级LockSupportparkunpark方法,并且如果您LockSupport.unpark在等待 on 的线程上调用Condition,则无法将此操作与虚假唤醒区分开来。

Slightly refactoring the OP's snippet,

稍微重构OP的片段,

public class Spurious {

    private static class AwaitingThread extends Thread {

        @Override
        public void run() {
            Lock lock = new ReentrantLock();
            Condition cond = lock.newCondition();
            lock.lock();
            try {
                try {
                    cond.await();
                    System.out.println("Spurious wakeup!");
                } catch (InterruptedException ex) {
                    System.out.println("Just a regular interrupt.");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static final int AMOUNT_OF_SPURIOUS_WAKEUPS = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread awaitingThread = new AwaitingThread();
        awaitingThread.start();
        Thread.sleep(10000);
        for(int i =0 ; i < AMOUNT_OF_SPURIOUS_WAKEUPS; i++)
            LockSupport.unpark(awaitingThread);
        Thread.sleep(10000);
        if (awaitingThread.isAlive())
            System.out.println("Even after " + AMOUNT_OF_SPURIOUS_WAKEUPS + " \"spurious wakeups\" the Condition is stil awaiting");
        else
            System.out.println("You are using very unusual implementation of java.util.concurrent.locks.Condition");
    }
}

, and no matter how hard the unparking(main) thread would try to awake the awaiting thread, the Condition.await()method will never return in this case.

,并且无论 unparking(main) 线程多么努力地尝试唤醒等待线程,Condition.await()在这种情况下该方法将永远不会返回。

The spurious wakeups on Condition's awaiting methods are discussed in the javadoc of Conditioninterface. Although it does say that,

接口ConditionjavadocCondition中讨论了等待方法的虚假唤醒。虽然是这么说的,

when waiting upon a Condition, a spurious wakeup is permitted to occur

在等待条件时,允许发生虚假唤醒

and that

然后

it is recommended that applications programmers always assume that they can occur and so always wait in a loop.

建议应用程序程序员始终假设它们可能发生,因此始终在循环中等待。

but it later adds that

但后来补充说

An implementation is free to remove the possibility of spurious wakeups

一个实现可以自由地消除虚假唤醒的可能性

and AbstractQueuedSynchronizer's implementation of Conditioninterface does exactly that - removes any possibility of spurious wakeups.

andAbstractQueuedSynchronizerCondition接口实现正是这样做的——消除了任何虚假唤醒的可能性

This surely holds true for other ConditionObject's awaiting methods.

这肯定适用于 otherConditionObject的等待方法。

So, the conclusionis :

所以,结论是:

we should always call Condition.awaitin the loop and check if the condition does not hold, but with standard, OpenJDK, Java Class Library is can never happen. Unless, again, you use very unusual Java Class Library (which must be very very unusual, because another well-known non-OpenJDK Java Class Libraries, currently almost extinct GNU Classpathand Apache Harmony, seems to have identical to standard implementation of Conditioninterface)

我们应该总是Condition.await在循环中调用并检查条件是否不成立,但是对于标准的 OpenJDK,Java 类库永远不会发生。除非您再次使用非常不寻常的 Java 类库(这一定非常非常不寻常,因为另一个著名的非 OpenJDK Java 类库,目前几乎绝迹GNU ClasspathApache Harmony,似乎与Condition接口的标准实现相同)