天天看點

Java多線程售票退票_線程虛假喚醒

一般而言線程調用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方法一定是要擷取到鎖後,才會傳回

參考 :