用Java来强制虚惊一场

这个问题不是关于虚假的唤醒是否真的很开心,因为这已经在这里全面讨论了: 虚假的唤醒是否真的发生? 因此,这也不是关于,为什么我必须围绕我的wait声明。 这是关于什么的:

我想构build一个虚构的唤醒事件。 我在上面提到的问题中学到的是:

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

所以看来,这只会工作在一台Linux机器,实际上我有Ubuntu 11.04 – 64位。 我已经写了一个Java程序,其中有一个线程正在等待一个条件,但是没有循环,而另一个线程正在等待并由另一个线程得到通知。 我认为在一个JVM中启动所有三个线程将强制上述情况,但似乎并非如此。

有没有人有另一个想法如何在Java中构build这样的情况?

你不能强制一个虚假的唤醒,但是对于正在运行的线程,一个虚假的唤醒与一个普通的唤醒没有区别(事件的来源是不同的,但事件本身是相同的)

要模拟虚假的唤醒,只需调用notify() ;

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

“虚假唤醒”是一个热门话题,涵盖了该领域的所有实现细节。 因此很难弄清楚“真实”的虚假唤醒是什么以及为什么另一个虚拟“不真实” – 更不用说这个实现细节来自哪一层。 从“kernel”,“system library(libc)”,“JVM”,“Java standart library(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! 

这里的实现看起来像我所期望的那样:使用条件的线程首先被唤醒。

最后一点:原理的思想来自java.util.concurrent.ArrayBlockingQueue为什么在await()调用周围使用“while”循环而不是“if”? ,虽然我的解释是不同的,代码是从我自己。

你所引用的原始问题(迄今为止,维基百科文章)指出,虚假的唤醒发生在Linux的pthread实现中, 因为进程被告知的副作用 。 从你的问题看来,我错过了用Object.notify()(这是java内部线程间通信方法)错过了“signal”(这是linux进程间通信方法)。

如果你想观察虚假唤醒 – 你必须运行你的Java程序,并尝试发送一些信号。

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

所以(至少对我而言),Linux信号在什么情况下会导致Java虚假唤醒仍然不清楚。

我发现一个重现器,强制在Java 错误6454029虚假唤醒 。 它启动30,60,然后100对服务员/通知,并使他们等待并通知指定的次数。 它使用标准的Object.wait()和Object.notify()而不是更高级别的Lock和Condition对象。 我已经设法使用它来导致虚假的唤醒发生在我的Linux 64位机器与参数值1000000与Java 1.8.0-b132和1.6.0_45。 请注意,原来的文件管理器是在抱怨Windows XP,所以大概这也适用于至少一个窗口的味道。

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

所以,尽管规范提到虚假唤醒的可能性,但应该很难建立一个确定性的测试。 考虑到在JVM内部运行的内核线程会唤醒信号,您将唤醒多少个绿色线程? 一? 十? 没有? 谁知道。