看到各种锁相关的问题和(几乎)总是发现'循环,因为虚假的唤醒'术语1我想知道,有没有人经历过这样的唤醒(假设一个体面的硬件/软件环境为例)?

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

(1注意:我不是在质疑循环实践。)

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

如果我有下面的程序,我运行它:

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();
        }
    }
}

我能做些什么来唤醒这个等待,而不是永远等待一个随机事件?


卡梅伦·珀迪(Cameron Purdy)不久前写了一篇博文,讲述了自己被假醒问题困扰的经历。所以,是的,它发生了

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


维基百科上关于假醒的文章有这样的花边新闻:

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

概要:如果一个Linux进程收到信号,它的等待线程将享受一个漂亮的、热的伪唤醒。

我相信。这比通常给出的含糊的“为了性能”的理由更容易接受。


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


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

我研究了一些网上的资料,这不是Java的问题,而是一种普遍罕见但意料之中的行为。


回答标题中的问题-是的!这种情况确实会发生。虽然维基上的文章提到了很多关于虚假唤醒的事情,但我遇到的一个很好的解释是

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".

我读了这个来源的答案,发现它足够合理。也读

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

附注:上面的链接是我的个人博客,上面有关于虚假唤醒的更多细节。


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


回答警察的问题

我能做些什么来唤醒这个等待,而不是永远等待 对于一个随机事件?

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

不管虚假唤醒是否能在特定平台上发生,在OP代码片段的情况下,Condition.await()返回并看到“虚假唤醒!”这行代码是绝对不可能的。 在输出流中。

除非您正在使用非常奇特的Java类库

这是因为标准的,OpenJDK的ReentrantLock的方法newCondition()返回AbstractQueuedSynchronizer的条件接口的实现,嵌套的条件对象(顺便说一下,它是这个类库中条件接口的唯一实现), 条件对象的方法await()本身检查条件是否不保持,并且没有任何虚假的唤醒可以迫使该方法错误地返回。

顺便说一下,您可以自己检查它,因为一旦涉及到基于abstractqueuedsynchronizer的实现,就很容易模拟虚假唤醒。 AbstractQueuedSynchronizer使用低级的LockSupport的park和unpark方法,如果调用LockSupport。在等待条件的线程上unpark,此操作不能与虚假唤醒区分开来。

稍微重构一下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");
    }
}

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

在Condition接口的javadoc中讨论了Condition等待方法的伪唤醒。 尽管它说了,

当等待一个条件时,一个虚假的唤醒被允许发生

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

但后来又补充说

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

而AbstractQueuedSynchronizer的条件接口实现正是这样做的——消除了任何虚假唤醒的可能性。

这当然适用于其他ConditionObject的await方法。

所以,结论是:

我们应该总是调用条件。在循环中等待并检查条件是否不保持,但对于标准的,OpenJDK, Java类库是永远不会发生的。 除非,你使用非常不寻常的Java类库(这肯定非常非常不寻常,因为另一个著名的非openjdk Java类库,目前几乎灭绝的GNU Classpath和Apache Harmony,似乎有相同的条件接口的标准实现)