我有一个Java线程做这样的事情:
while (running) {
synchronized (lock) {
if (nextVal == null) {
try {
lock.wait();
} catch (InterruptedException ie) {
continue;
}
}
val = nextVal;
nextVal = null;
}
...do stuff with 'val'...
}
在其他地方,我设置了这样的值:
if (val == null) {
LOG.error("null value");
} else {
synchronized (lock) {
nextVal = newVal;
lock.notify();
}
}
偶尔(实际上每两千万次)nextVal将被设置为null。我已经抛出了记录消息,我可以看到执行的顺序如下:
我已经明确检查并且锁定第二次醒来,它没有被打断。
我在这里做错了吗?
答案 0 :(得分:4)
是的,Thread
自发地醒来。这在the Javadoc中明确说明:
“线程也可以在没有被通知,中断或超时的情况下唤醒,即所谓的虚假唤醒。”
您需要在循环中wait
。这也在javadoc中明确提到:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
在你的情况下:
while (running) {
synchronized (lock) {
while (nextVal == null) {
try {
lock.wait();
} catch (InterruptedException ie) {
//oh well
}
}
val = nextVal;
nextVal = null;
}
...do stuff with 'val'...
}
答案 1 :(得分:1)
虚假唤醒是相当普遍的,因此在循环内的条件下始终建议wait()
。
按如下方式更改您的代码:
while (nextVal == null) {
try {
lock.wait();
} catch (InterruptedException ignored) {
}
}
特定于您共享的代码:while
还可以帮助您避免在代码命中continue;
时释放和重新获取相同锁定的不必要开销
参考文献:
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Object.html#wait()
来自
public final void wait()
文档:
...中断和虚假唤醒是可能的,并且这个方法应该总是在循环中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
答案 2 :(得分:1)
这可能是一次虚假的唤醒,但这不是唯一可能的原因。这肯定是你的逻辑问题。你需要把等待放在一个重新测试条件的循环中。
当线程从等待中唤醒时,它不再具有锁定。它在开始等待时释放了锁,它需要重新获取锁才能继续。由于线程亲和性(这可能是你的代码大部分时间都在工作的原因),刚刚唤醒的线程通常可能是下一个线程,但仍然有可能不是这样;在唤醒线程可以获取锁定之前,另一个线程可以进入并锁定锁定,执行其操作并将nextVal保留为null。这意味着线程在等待之前进行的null测试不再相关。一旦锁定,你必须再次回来测试。
更改代码以使用循环,例如:
synchronized(lock) {
while (nextVal == null) {
lock.wait();
}
...
这种方式是在线程具有锁定时进行测试,并且在while循环下面的块中发生的任何事情都可以确定nextVal实际上不是null。