今天看JDK文檔中的Object.wait()方法,有一段提到:
對於某一個參數的版本,實現中斷和虛假喚醒是可能的,而且此方法應始終在循環中使用:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
大致意思就是,wait()方法需要在循環中使用,以避免實現虛假喚醒。那什么是虛假喚醒,話不多說,貼一段代碼來看:
-----------------------------------------------------代碼分隔線-------------------------------------------------------
Thread-1:
while(true) {
obj = queue.get(); //2
// 3
}
Thread-2:
synchronized(lock) {
// 代碼一
while(queue.isEmpty()) {
lock.wait();
// 4
}
obj = queue.get(); // 5
// 代碼二(可能導致虛假喚醒)
lock.wait();
// 4
obj = queue.get(); // 5
}
Thread-3:
synchronized(lock) {
queue.add(obj);
lock.notify(); // 1
}
-----------------------------------------------------代碼分隔線-------------------------------------------------------
從上面的偽代碼看到:
一、有三個線程,對同一數據進行訪問
二、線程1未對數據進行安全訪問
三、初始狀態為:queue.size()==0,代碼執行順序為: 1-->2-->3-->4-->5
四、線程2的目的是當queue為空時等待,不為空時取出數據進行處理
五、線程2的代碼一、代碼二只能使用其中一種方式。
結論:當線程2使用代碼一時沒有問題,當使用代碼二時,此時線程被喚醒,但仍然取不到數據,這就是虛假喚醒。
虛假喚醒發生的條件為:
1、當一個數據存在三個及以上的線程競爭訪問時(必要條件)
2、至少有一個線程沒有對數據進行加鎖訪問(充分條件,使得虛假喚醒發生可能)
當滿足兩個條件才可能發生虛假喚醒。僅僅是可能,如果代碼執行順序為:1-->4-->5-->2-->3,線程二可以取到數據,也就不存在虛假喚醒。
解決虛假喚醒(以下任意一種方式即可):
1、所有的線程訪問數據時都加鎖(線程一就沒有加鎖)
2、在循環中等待(線程2中的代碼一)