“虚假唤醒”是一个大杂烩,涵盖了该领域的任何实现细节。因此,很难弄清楚什么是“真实”的虚假唤醒,以及为什么另一个是“不真实的” - 更不用说这个实现细节起源于哪一层了。从“内核”,“系统库(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!
在这里,实现似乎像我预期的那样:首先唤醒使用该条件的线程。
最后说明:这个原则的想法来自为什么java.util.concurrent.ArrayBlockingQueue使用“while”循环而不是“if”来调用wait()?尽管我的解释是不同的,代码来自我自己。