Java线程自发地醒来

时间:2013-04-26 18:24:17

标签: java multithreading thread-safety

我有一个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。我已经抛出了记录消息,我可以看到执行的顺序如下:

  1. thread1将nextVal设置为newVal
  2. thread1调用lock.notify()
  3. thread2从lock.wait()
  4. 中醒来
  5. thread2将val设置为nextVal
  6. thread2将nextVal设置为null
  7. thread2用val
  8. 做东西
  9. thread2调用lock.wait()
  10. thread2从lock.wait()中醒来
    • 没有其他线程调用lock.notify()并且thread2没有被中断
  11. thread2将val设置为nextVal(为空)
  12. 我已经明确检查并且锁定第二次醒来,它没有被打断。

    我在这里做错了吗?

3 个答案:

答案 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。