一般而言線程調(diào)用wait()方法后,需要其他線程調(diào)用notify,notifyAll方法后,線程才會從wait方法中返回, 而虛假喚醒(spurious wakeup)是指線程通過其他方式,從wait方法中返回。
下面是一個買票退票的操作例子,買票時,線程A買票,如果發(fā)現(xiàn)沒有余票,則會調(diào)用wait方法,線程進(jìn)入等待隊列中,線程B進(jìn)行退票操作,余票數(shù)量加一,然后調(diào)用notify 方法通知等待線程,此時線程A被喚醒執(zhí)行購票操作。
從程序的順序性來看 if (remainTicketNum<=0)沒有問題,但是為什么會出現(xiàn)虛假喚醒呢?
因為wait方法可以分為三個操作:
(1)釋放鎖并阻塞
(2)等待條件cond發(fā)生
(3)獲取通知后,競爭獲取鎖
假設(shè)此時有線程A,C買票,線程A調(diào)用wait方法進(jìn)入等待隊列,線程C買票時發(fā)現(xiàn)線程B在退票,獲取鎖失敗,線程C阻塞,進(jìn)入阻塞隊列,線程B退票時,余票數(shù)量+1(滿足條件2 等待條件發(fā)生),線程B調(diào)用notify方法后,線程C馬上競爭獲取到鎖,購票成功后余票為0,而線程A此時正處于wait方法醒來過程中的第三步(競爭獲取鎖獲取鎖),當(dāng)線程C釋放鎖,線程A獲取鎖后,會執(zhí)行購買的操作,而此時是沒有余票的。
解決的辦法是條件判斷通過while(remainTicketNum<=0)來解決,但是有個問題是如果一直沒有退票操作線程N(yùn)otify,while語句會一直循環(huán)執(zhí)行下去,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方法一定是要獲取到鎖后,才會返回
參考 :