Java线程自动唤醒
我有一个Java线程,它执行如下操作:Java线程自动唤醒,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我有一个Java线程,它执行如下操作: while (running) { synchronized (lock) { if (nextVal == null) { try { lock.wait(); } catch (InterruptedException ie) { continue; } } v
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();
}
}
在其他地方,我设置如下值:
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。我已经输入了日志消息,我可以看到执行顺序如下所示:
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();
}
}
- 没有其他线程调用lock.notify(),thread2也没有被中断
我在这里做错什么了吗?是的,
线程
会自动醒来。这一点在以下文件中明确说明:
线程也可以在不被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒
您需要在循环中等待。javadoc中也明确提到了这一点:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
虚假唤醒非常常见,因此建议在循环中的某个条件下总是
wait()
按如下方式更改代码:
while (nextVal == null) {
try {
lock.wait();
} catch (InterruptedException ignored) {
}
}
特定于您共享的代码:while
还可以帮助您避免在代码点击continue时释放和重新获取相同锁的不必要的开销代码>
参考文献:
从public final void wait()
文档:
... 中断和虚假唤醒是可能的,这种方法应始终在循环中使用:
synchronized(obj){
而()
obj.wait();
…//执行适合条件的操作
}
这可能是虚假的唤醒,但这不是唯一可能的原因。这绝对是你逻辑上的问题。您需要将等待放在重新测试条件的循环中
当线程从等待中醒来时,它不再具有锁。它在开始等待时释放了锁,需要重新获取锁才能继续。由于线程相关性(这可能是代码大部分时间工作的原因),刚刚醒来的线程通常是下一个线程,但仍然有可能不是;另一个线程可能会进入并阻塞锁,在被唤醒的线程获取锁之前,完成它的工作并将nextVal保留为null。这意味着线程在等待之前进行的null测试不再相关。一旦你有了锁,你必须回去再次测试
将代码更改为使用循环,如:
synchronized(lock) {
while (nextVal == null) {
lock.wait();
}
...
这样,测试是在线程拥有锁的情况下进行的,while循环下面的块中发生的任何事情都可以确定nextVal真的不是null。+1对于一个写得很好的解释,但是-1对于没有更仔细地阅读Javadoc:-)IIRC,不能保证线程不会从等待中醒来,而等待条件仍然不满足。您必须始终检查条件,如果不满足,则重新执行等待。(原因与实现“唤醒”逻辑的复杂性有关。如果没有大量昂贵的同步,确保从不出现虚假唤醒几乎是不可能的。)哇,我做了很多线程,这是我第一次注意到这一点!感谢所有温和的提醒,重新阅读文档:-这是线程的一个特点,您可以运行无限次减去delta次,但仍然可能包含bug。我建议将其作为线程问题的最终文本。由于多线程固有的复杂性,正常的方法是无用的,您无法编写和测试;如上所述,完全测试并发代码几乎是不可能的-您必须事先知道常见的陷阱。我应该注意,'while(nextVal==null){'解决方案对我来说不起作用,因为我忽略了这个线程通过一个方法停止的细节:synchronized(lock){running=true;lock.notify();}所以我不能在'nextVal==null'上旋转。相反,我添加了:if(nextVal==null)continue;在'val=nextVal'之前,以确保在循环的每次行程中都检查'running'
。在其他地方检查运行
标志,然后返回。您甚至可以在中断中添加一些逻辑,然后中断
线程停止。是的,中断是停止运行线程的正确方法。使用单独的布尔
标志方法充满了陷阱。我希望正在运行的
在此处标记为易失性!