多线程 while 和 if 判断条件的区别
让我们看一个这样一种情况:
class WaitPerson implements Runnable {
private Restaurant restaurant;
public WaitPerson(Restaurant r) { restaurant = r; }
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
while(restaurant.meal == null)
wait(); // ... for the chef to produce a meal
}
print("Waitperson got " + restaurant.meal);
synchronized(restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll(); // Ready for another
}
}
} catch(InterruptedException e) {
print("WaitPerson interrupted");
}
}
}
其中,while(restaurant.meal == null) 中使用了 while。可以换成 if 吗?
不行。原因是,在 synchronized 这个同步块中,当任务进入到 同步块语句中先进行一次条件的判断。如果不符合,线程进入 wait()状态。这里注意 wait() 和 sleep() 的区别,调用 wait() 会释放该对象的锁,并将线程挂起。而 sleep() 则不释放锁,只是将线程挂起。当锁释放后,下一个任务进入该锁中,此时如果它改变 restaurant.meal 的值(即改变先前的判断条件),然后再调用 notifyAll() 。调用 notifyAll() 将唤醒此时该锁中被挂起的线程,当它从这个锁中离开后,之前那个 wait()状态的线程将可能重新进入这个对象的锁中,此时,如果没有 while判断,它在 wait()之后将直接执行下面的流程,而不必再次判断条件是否符合。而实际上上一个任务中已经将判断条件改变了,被唤醒的任务不应该直接往下执行,而应该重新判断条件是否符合。所以此时必须用 while。
你必须用一个检查感兴趣的条件的 while循环包围 wait()。这样做的本质就是检查所感兴趣的特定条件,如果在不满足的条件下返回到 wait()中。惯用的方法就是使用 while来编写这种代码。如果你使用 if 则无法应付下面的情况:
可能有多个任务在等待相同一个锁,而第一个唤醒的任务可能会改变这种状态(即使你没有这么做,有人会通过继承你的类去这么做)。如果属于这种情况,那么这个任务应当被再次挂起,直至其感兴趣的条件发生变化。
在这个任务从 wait()被唤醒的时刻,有可能会有某个地方的任务已经做出了改变,从而使这个任务在此刻不能执行,或者执行其操作已经显得无关紧要。此时,应该通过再次调用 wait()来将其重新挂起。
也有可能某些任务出于不同的原因在等待你的对象上的锁(在这种情况下,你必须使用 notifyAll() )。在这种情况下,你需要检查是否已经由正确的原因唤醒,如果不是,就再次调用 wait()。