在多線程系統中,不同的線程執行不同的任務;如果這些任務之間存在聯繫,那麼執行這些任務的線程之間就必須能夠通信,共同協調完成系統任務。這就產生了生產者和消費者的問題
簡單例子 |
新建資源類
class bed{
private int number=0;
public synchronized void increment()throws Exception{
// 1.判斷
if (number!=0){
wait();
}
// 2.幹活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 3.通知
this.notifyAll();
}
public synchronized void decrement()throws Exception{
// 1.判斷
if (number==0){
wait();
}
// 2.幹活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 3.通知
this.notifyAll();
}
}
新建執行類
public class ProdConsumerDemo04 {
public static void main(String[] args) {
Bed bed=new Bed ();
new Thread(()->{
for (int i = 0; i <=10 ; i++) {
try {
bed.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <=10 ; i++) {
try {
bed.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
}
}
執行結果
從上圖看,目前線程生產一個消費一個,結果是正確的,但是當線程增加到4個,加上sleep方法模擬卡頓時,如下圖
此時執行結果就出現了問題,不再是生產一個消費一個
分析
這是爲什麼呢?首先明確幾點
1.synchronized 在線程wait()時將會暫時失去加鎖效果
2.
假設此時有一個工廠,外面有4個工人,兩個負責生產,兩個負責搬運,工廠一次只允許進入一個工人生產,而且如果工人進入時發現有產品,就暫時等待,不生產產品,並且通知工人進來,我們把4個工人分別命名爲a1,a2.b1,b2
下圖中,a1工人先進入工廠,生產了1個產品,此時a1通知外面可以進來一個工人了,正常情況下應該進入一個搬運工人b1,或者b2
但是此時a2搶到了線程,但是由於產品未被消費,第二個工人仍在等待,同時通知可以進來了,此時外面有3個人a1,b1,c1,有一種可能a1又進來了,此時工廠中有兩個生產的工人,a1進來時有產品,所以a1也等待了,同時通知外面,可以再進來人
工廠中此時有三個人,b1進來之後消費了一個產品,同時喚醒所有工人,但是由於a1,a2都在此等待,而且都通過了有是否有產品的判斷,他不會再判斷一回,直接開始生產,所以此時產品的數量爲2
這是因爲出現了虛假喚醒,什麼是虛假喚醒呢?
爲防止虛假喚醒,應該將資源類的if判斷換爲while,JDK1.8官網上給出如下解釋
像在一個參數版本中,中斷和虛假喚醒是可能的,並且該方法應該始終在循環中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}