当多个线程由于不同原因而在监视器的等待队列中等待时,需要将wait保护在while循环中:
while (conditionIsNotTrue)
wait()
这样可以确保线程被唤醒的确是由于所等待的条件变为真了。但是如果条件队列中的所有线程都在等待同一个条件,那么是否可以将wait包含在if判断中呢?
if (conditionIsNotTrue)
wait()
不能,原因有三:
1.子类极易破坏原有的代码同步策略。子类有可能在同一监控器上为不同的条件而等待,从而父类的等待队列中线程所等待的条件就不再唯一了,例如下面的例子中,调用Room.comeIn()方法的线程原本只会因为isSeatsAreAvailable条件不能满足而等待,因此使用if可以认为是安全的。但是正如OfficeRoom所作的,调用OfficeRoom.bossComeToOffice()的线程会因为isOfficeEmpty不能满足而阻塞,同样线程也会因为isSeatsAreAvailable而阻塞。这是只用if判断就是不安全的了。
2. 如果将Romm声明为final类型呢?仍然是不安全的。尽管线程阻塞的原因一定是唯一的了,但是唤醒线程的原因并不唯一。 除了执行Room.releaseSeat()的线程会唤醒等待isSeatsAreAvailable的线程外,操作系统还会产生“假唤醒”,在毫无理由的情况下,把等待isSeatsAreAvailable的线程唤醒。对于这种行为,JVM规范并未做任何限制。因此,永远不能假设线程从wait()中被唤醒后会处于何种状态,wait()一定要在while循环中被保护起来。
----------
class Room {
public synchronized void comeIn() {
if (!isSeatsAreAvailable()) {
wait();
}
doComeIn();
}
public synchronized void releaseSeat() {
notifyAll();
}
}
class OfficeRoom extends Room {
public synchronized void bossComeToOffice() {
if (!isOfficeEmpty()) {
wait();
}
}
public synchronized void emptyOffice() {
notifyAll();
}
}
----------
3. 代码不健壮。由于wait,notify,notifyAll都是公共方法,所以客户端代码很容易(或故意)调用notify/notifyAll,从而导致代码失败。
综上,wait()方法永远要被放在while循环中。或者说,对于一个从wait等待中被唤醒的线程,在继续执行前,一定要再次检查它的先验条件是否已经得到满足。否则,你的代码就不是线程安全的了!
while (conditionIsNotTrue)
wait()
这样可以确保线程被唤醒的确是由于所等待的条件变为真了。但是如果条件队列中的所有线程都在等待同一个条件,那么是否可以将wait包含在if判断中呢?
if (conditionIsNotTrue)
wait()
不能,原因有三:
1.子类极易破坏原有的代码同步策略。子类有可能在同一监控器上为不同的条件而等待,从而父类的等待队列中线程所等待的条件就不再唯一了,例如下面的例子中,调用Room.comeIn()方法的线程原本只会因为isSeatsAreAvailable条件不能满足而等待,因此使用if可以认为是安全的。但是正如OfficeRoom所作的,调用OfficeRoom.bossComeToOffice()的线程会因为isOfficeEmpty不能满足而阻塞,同样线程也会因为isSeatsAreAvailable而阻塞。这是只用if判断就是不安全的了。
2. 如果将Romm声明为final类型呢?仍然是不安全的。尽管线程阻塞的原因一定是唯一的了,但是唤醒线程的原因并不唯一。 除了执行Room.releaseSeat()的线程会唤醒等待isSeatsAreAvailable的线程外,操作系统还会产生“假唤醒”,在毫无理由的情况下,把等待isSeatsAreAvailable的线程唤醒。对于这种行为,JVM规范并未做任何限制。因此,永远不能假设线程从wait()中被唤醒后会处于何种状态,wait()一定要在while循环中被保护起来。
----------
class Room {
public synchronized void comeIn() {
if (!isSeatsAreAvailable()) {
wait();
}
doComeIn();
}
public synchronized void releaseSeat() {
notifyAll();
}
}
class OfficeRoom extends Room {
public synchronized void bossComeToOffice() {
if (!isOfficeEmpty()) {
wait();
}
}
public synchronized void emptyOffice() {
notifyAll();
}
}
----------
3. 代码不健壮。由于wait,notify,notifyAll都是公共方法,所以客户端代码很容易(或故意)调用notify/notifyAll,从而导致代码失败。
综上,wait()方法永远要被放在while循环中。或者说,对于一个从wait等待中被唤醒的线程,在继续执行前,一定要再次检查它的先验条件是否已经得到满足。否则,你的代码就不是线程安全的了!