一般而言線程調用wait()方法後,需要其他線程調用notify,notifyAll方法後,線程才會從wait方法中傳回, 而虛假喚醒(spurious wakeup)是指線程通過其他方式,從wait方法中傳回。
下面是一個買票退票的操作例子,買票時,線程A買票,如果發現沒有餘票,則會調用wait方法,線程進入等待隊列中,線程B進行退票操作,餘票數量加一,然後調用notify 方法通知等待線程,此時線程A被喚醒執行購票操作。
從程式的順序性來看 if (remainTicketNum<=0)沒有問題,但是為什麼會出現虛假喚醒呢?
因為wait方法可以分為三個操作:
(1)釋放鎖并阻塞
(2)等待條件cond發生
(3)擷取通知後,競争擷取鎖
假設此時有線程A,C買票,線程A調用wait方法進入等待隊列,線程C買票時發現線程B在退票,擷取鎖失敗,線程C阻塞,進入阻塞隊列,線程B退票時,餘票數量+1(滿足條件2 等待條件發生),線程B調用notify方法後,線程C馬上競争擷取到鎖,購票成功後餘票為0,而線程A此時正處于wait方法醒來過程中的第三步(競争擷取鎖擷取鎖),當線程C釋放鎖,線程A擷取鎖後,會執行購買的操作,而此時是沒有餘票的。
解決的辦法是條件判斷通過while(remainTicketNum<=0)來解決,但是有個問題是如果一直沒有退票操作線程Notify,while語句會一直循環執行下去,CPU消耗巨大
public class SpuriousWakeUp {
static Object lock=newObject();
static int remainTicketNum=0;
public void buyTicket() {
synchronized(lock) {
while(remainTicketNum<=0) {//if (remainTicketNum<=0)虛假喚醒
try{
lock.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}}
remainTicketNum--;
System.out.println(Thread.currentThread().getName() +"購買成功");
}}
public voidreturnTicket() {
synchronized(lock) {
remainTicketNum++;
lock.notify();
System.out.println(Thread.currentThread().getName() +"退票成功");
}}}
PS:wait方法一定是要擷取到鎖後,才會傳回
參考 :