在Java中强制虚假唤醒

时间:2011-07-14 22:51:42

标签: java linux multithreading pthreads posix

这个问题不是关于,虚假的唤醒是否真的很开心,因为这已经在这里详细讨论了:Do spurious wakeups actually happen?因此这也不是关于,为什么我必须围绕我的{{1声明。这是关于:

我想构建一个案例,其中发生虚假的唤醒。到目前为止,我在上面提到的问题中学到的是:

  

如果发出Linux进程的信号,它的等待线程将各自享受一个   好的,热的虚假唤醒。

所以看起来这只能用于linux机器,实际上我有Ubuntu 11.04 - 64位。我编写了一个Java程序,其中一个线程正在等待一个条件,但是没有循环和另一个类,线程只是等待并被另一个线程通知。我认为在一个JVM中启动所有三个线程会强制执行上述情况,但似乎情况并非如此。

还有其他人想过如何在Java中构建这样的案例吗?

7 个答案:

答案 0 :(得分:17)

你不能强制进行虚假唤醒,但是对于正在运行的线程,虚假唤醒与常规唤醒无法区分(事件的来源不同,但事件本身是相同的)

要模拟虚假唤醒,只需拨打notify();

即可

调用interrupt()不合适,因为这样做会设置中断标志,并且在虚假唤醒之后,中断标志设置

答案 1 :(得分:9)

“Spurious wakeup”是一个hotchpotch,涵盖了该领域的任何实现细节。因此,很难弄清楚“真正的”虚假唤醒是什么以及为什么另一个是“不真实的” - 更不用说这个实现细节来自哪个层。从“内核”,“系统库(libc)”,“JVM”,“Java标准库(rt.jar)”或构建在该堆栈之上的自定义框架中选择任何一个。

以下程序使用java.util.concurrent内容显示虚假唤醒:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SpuriousWakeupRWLock {
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    static int itemsReady;

    public static void main(String[] args) throws Exception {

        // let consumer 1 enter condition wait
        new ConsumerOne().start();
        Thread.sleep(500);

        lock.lock();
        try {
            // let consumer 2 hit the lock
            new ConsumerTwo().start();
            Thread.sleep(500);

            // make condition true and signal one (!) consumer
            System.out.println("Producer: fill queue");
            itemsReady = 1;
            condition.signal();
            Thread.sleep(500);
        }
        finally {
            // release lock
            lock.unlock();
        } 

        System.out.println("Producer: released lock");
        Thread.sleep(500);
    }

    abstract static class AbstractConsumer extends Thread {
        @Override
        public void run() {
            lock.lock();
            try {
                consume();
            } catch(Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        abstract void consume() throws Exception;
    }

    static class ConsumerOne extends AbstractConsumer {
        @Override
        public void consume() throws InterruptedException {
            if( itemsReady <= 0 ){      // usually this is "while"
                System.out.println("One: Waiting...");
                condition.await();
                if( itemsReady <= 0 )
                    System.out.println("One: Spurious Wakeup! Condition NOT true!");
                else {
                    System.out.println("One: Wakeup! Let's work!");
                    --itemsReady;
                }
            }
        }
    }

    static class ConsumerTwo extends AbstractConsumer {
        @Override
        public void consume() {
            if( itemsReady <= 0 )
                System.out.println("Two: Got lock, but no work!");
            else {
                System.out.println("Two: Got lock and immediatly start working!");
                --itemsReady;
            }
        }
    }
}

输出:

One: Waiting...
Producer: fill queue
Producer: released lock
Two: Got lock and immediatly start working!
One: Spurious Wakeup! Condition NOT true!

使用过的JDK是:

java version "1.6.0_20"
OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2)
OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)

它基于java.util.concurrent中的一个实现细节:标准Lock有一个等待队列,Condition有另一个等待队列。如果发出条件信号,则将发出信号的线程从条件的队列移动到锁的队列中。实现细节:它在队列末尾移动 。如果另一个线程已经在锁定队列中等待并且第二个线程没有访问条件变量,则该线程可以“窃取”该信号。如果实现将第一个线程放在第二个线程之前,则不会发生这种情况。这个“奖金”可能/将基于第一个线程已经获得锁定一次的事实以及与相同锁定相关联的条件中的等待时间被记入该线程。

我将此定义为“虚假”,因为

  • 该条件仅发出一次信号,
  • 只有一个线程被条件唤醒
  • 但被条件唤醒的线程发现它不是真的
  • 另一个线程从未触及过这种情况,因此是“幸运但无辜”
  • 一个稍微其他的实现会阻止这种情况。

此代码使用Object.wait()

演示了最后一点
public class SpuriousWakeupObject {
    static Object lock = new Object();
    static int itemsReady;

    public static void main(String[] args) throws Exception {

        // let consumer 1 enter condition wait
        new ConsumerOne().start();
        Thread.sleep(500);

        // let consumer 2 hit the lock
        synchronized (lock) {
            new ConsumerTwo().start();
            Thread.sleep(500);

            // make condition true and signal one (!) consumer
            System.out.println("Producer: fill queue");
            itemsReady = 1;
            lock.notify();

            Thread.sleep(500);
        } // release lock
        System.out.println("Producer: released lock");
        Thread.sleep(500);
    }

    abstract static class AbstractConsumer extends Thread {
        @Override
        public void run() {
            try {
                synchronized(lock){
                    consume();
                }
            } catch(Exception e){
                e.printStackTrace();
            }
        }
        abstract void consume() throws Exception;
    }

    static class ConsumerOne extends AbstractConsumer {
        @Override
        public void consume() throws InterruptedException {
            if( itemsReady <= 0 ){      // usually this is "while"
                System.out.println("One: Waiting...");
                lock.wait();
                if( itemsReady <= 0 )
                    System.out.println("One: Spurious Wakeup! Condition NOT true!");
                else {
                    System.out.println("One: Wakeup! Let's work!");
                    --itemsReady;
                }
            }
        }
    }

    static class ConsumerTwo extends AbstractConsumer {
        @Override
        public void consume() {
            if( itemsReady <= 0 )
                System.out.println("Two: Got lock, but no work!");
            else {
                System.out.println("Two: Got lock and immediatly start working!");
                --itemsReady;
            }
        }
    }
}

输出:

One: Waiting...
Producer: fill queue
Producer: released lock
One: Wakeup! Let's work!
Two: Got lock, but no work!

这里的实现似乎按照我的预期进行:使用条件的线程首先被唤醒。

最后注意事项:原则的想法来自Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()?,虽然我的解释不同而且代码来自我自己。

答案 2 :(得分:3)

您提到的原始问题(至于维基百科文章)说,虚假的唤醒发生在pthread的linux实现中,作为发出信号的过程的副作用。从你的问题看来,你错过了使用Object.notify()(这是java内部线程间通信方法)的“信号”(这是linux进程间通信方法)。

如果你想观察虚假的唤醒 - 你必须运行你的java程序并尝试向它发送一些信号。

答案 3 :(得分:2)

我在Linux上尝试了一个简单的测试,发送一个简单的Java进程信号(如QUIT,STOP,CONT等)。这些似乎没有造成虚假的唤醒。

所以(至少对我而言)目前还不清楚Linux信号会在什么条件下导致Java中的虚假唤醒。

答案 4 :(得分:1)

我找到了一个强制Java bug 6454029中的虚假唤醒的重现器。它启动30,60,然后100对服务员/通知程序,并使它们等待并通知指定的次数。它使用标准的Object.wait()和Object.notify()而不是更高级别的Lock和Condition对象。我已经设法使用它在我的linux 64位机器上发生虚假唤醒,参数值为1000000,包括java 1.8.0-b132和1.6.0_45。请注意,原始文件管理器抱怨Windows XP,因此可能这至少适用于一种窗口。

答案 5 :(得分:0)

AFAIK,Sun的JVM使用“绿色线程”,也称为用户级线程。这意味着JVM线程和内核线程实际上不必映射1对1。因此,除非规范说明,否则我不明白为什么JVM会符合POSIX行为。

因此,即使规范提到虚假唤醒的可能性,也应该很难建立一个确定性的测试,导致一个。考虑到JVM内部运行的内核线程在信号上唤醒,你会唤醒多少绿色线程?一?十?没有?谁知道呢。

答案 6 :(得分:0)

虚假唤醒的数量数量直接成比例  系统中的处理器内核数量

在具有24个以上内核的系统上,应该容易遇到虚假的唤醒。 (有些博客作者甚至声称在这些系统上大约40%的等待被中断。但是,方程式中肯定还有其他因素需要考虑,因此很难在其上加上数字。)