看到各种锁相关的问题和(几乎)总是发现'循环,因为虚假的唤醒'术语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();
}
}
}
我能做些什么来唤醒这个等待,而不是永远等待一个随机事件?
回答警察的问题
我能做些什么来唤醒这个等待,而不是永远等待
对于一个随机事件?
,没有任何虚假的唤醒可以唤醒这个等待线程!
不管虚假唤醒是否能在特定平台上发生,在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,似乎有相同的条件接口的标准实现)