看到各种锁相关的问题和(几乎)总是发现'循环,因为虚假的唤醒'术语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();
        }
    }
}

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


当前回答

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

其他回答

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

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

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

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

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

回答警察的问题

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

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

不管虚假唤醒是否能在特定平台上发生,在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,似乎有相同的条件接口的标准实现)

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

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

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