6

我有一个 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 将设置为空。我已经在记录消息中折腾了,我可以看到执行顺序如下所示:

  1. thread1 将 nextVal 设置为 newVal
  2. thread1 调用 lock.notify()
  3. thread2 从 lock.wait() 中唤醒
  4. thread2 将 val 设置为 nextVal
  5. thread2 将 nextVal 设置为 null
  6. thread2 用 val 做事
  7. thread2 调用 lock.wait()
  8. thread2 从 lock.wait() 中唤醒
    • 没有其他线程调用 lock.notify() 并且 thread2 没有被中断
  9. thread2 将 val 设置为 nextVal(为空)
  10. 等等

我已经明确检查过,锁第二次被唤醒,它没有被中断。

我在这里做错了吗?

4

3 回答 3

4

是的,Threads自然醒了。这在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'...
}
于 2013-04-26T18:33:41.313 回答
1

这可能是虚假唤醒,但这不是唯一可能的原因。肯定是你的逻辑有问题。您需要将等待放在重新测试条件的循环中。

当一个线程从等待中唤醒时,它就不再拥有锁了。它在开始等待时释放了锁,它需要重新获取锁才能继续。由于线程关联性,刚唤醒的线程通常可能排在下一个线程(这可能是您的代码大部分时间都可以工作的原因),但仍有可能不是;在被唤醒的线程可以获取锁之前,另一个线程可以进来并获得锁,做它的事情并让 nextVal 为空。这意味着线程在等待之前对 null 的测试不再相关。获得锁后,您必须返回并再次测试。

更改代码以使用循环,例如:

synchronized(lock) {
    while (nextVal == null) {
        lock.wait();
    }
   ...

这样测试是在线程有锁的时候进行的,无论在 while 循环下面的块中发生什么,都可以确定 nextVal 真的不为空。

于 2013-04-26T19:05:42.070 回答
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
 }
于 2013-04-26T18:34:21.630 回答