生產者消費模式 : 顧名思義有生產者、消費者、資源 三個對象。生產者生產資源,消費者消費資源。
類似於工廠流水線,多條生產線(生產者),多條包裝線(消費線)。
前面有介紹線程的 等待喚醒機制 我們可以回顧下,一條輸入線程(生產者),一條輸出線程(消費者),不同線程對同一資源進行操作,只不過操作的工作不一致。
回顧下等待喚醒機制的代碼(2條線程)
//資源文件
class Resources
{
private String name;
private int count = 1;
private boolean flag = false; //標誌位
//生產函數
public void synchronized setName(String name)
{
if(flag)
{ try{this.wait();} try(Exception e){} }
this.name = name + count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
falg = true;
this.notify();
}
//消費函數
public void synchronized out()
{
if(!flag)
{ try{this.wait();} try(Exception e){} }
System.out.println(Thread.currentThread().getName()+" <----消費者---> "+this.name);
flag = false;
this.notify();
}
}
// 生產者文件
class Producter implements Runnable
{
private Resources res;
public Producter(Resources res)
{
this.res = res;
}
//實現run方法
public void run()
{
res.setName("蘋果");
}
}
//消費者文件
class Consumer implements Runnable
{
private Resources res;
public Consumer(Resources res)
{
this.res = res;
}
//實現run方法
public void run()
{
res.out();
}
}
//main方法
public static void main(String [] args)
{
//資源
Resources res = new Resources();
//線程對象
Thread t1 = new Thread(new Producter(res));
Thread t2 = new Thread(new Consumer(res));
//開啓線程
t1.start();
t2.start();
}
以上代碼運行是們問題的,正常的生產線程生產一個資源,消費線程消費一個資源,有序間隔輸出。但是當我們增加線程數繼續運行的時候,就發現打印的結果不對了。
在main方法修改如下
public static void main(String[] args)
{
//創建資源
Resource res = new Resource();
//生產者線程
Thread t1 = new Thread(new Producter(res));
Thread t2 = new Thread(new Consumer(res));
//消費者線程
Thread t3 = new Thread(new Producter(con));
Thread t4 = new Thread(new Producter(con));
//開啓線程
t1.start();
t2.start();
t3.start();
t4.start();
}
.
截取部分輸出結果不匹配截圖如下
.
.
生產2個,只消費了其中1個。或者消費兩個,只生產一個。……爲什麼會出現如上問題呢?我們可以分析下問題
.
假設代碼執行流程如下(解釋下生產2次,消費一次)
生產線程 : t1、t2
消費線程 :t3、t4
(1) 假設生產者率先獲取cpu的執行權,生產者有2個。假設t1先獲取執行權,t1判斷標誌位flag = false,先生產一個(打印一次),後將flag = true,並notify()後。此時t1還持有執行權,繼續回來判斷flag = true, 符合條件,t1就直接wait了,放棄資格。此時wait的線程就直接進線程池(數據結構爲鏈表結構,先進先出)
(2) t2、t3、t4,假設生產者t2繼續獲取cpu執行權,判斷標誌位flag = true,符合條件,t2也wait了,放棄資格。(此時線程池裏有t1、t2)
(3) 只剩下t3、t4。 t3先獲取執行資格,判斷標誌位!falg = false,不符合條件,繼續往下執行,消費一個。並把標誌位設置成falg = false,並notofy()下。後,t3繼續享有執行權,回來繼續判斷標誌位 !flag = true 。符合條件,t3就直接wait了。失去執行資格,進入線程池。(此時線程池有t1、t2、t3)。
(4) 上一步驟 t3 執行完後,notify(),此時優先喚醒了生產線程t1。t1 線程在剛wait地方繼續往下執行(注意:t1不繼續判斷標誌位了),生產一個。並繼續notify(),此時喚醒的是t2線程,t2也不繼續判斷,繼續往下執行,又生產一個。t2喚醒的是消費者t3。t3 就消費一個。此時,就會產生生產兩個,只消費一個的現象。
其他情況自己類比解釋。
那問題來了? 爲什麼會造成如上問題呢 ?
根據上述執行步驟分析,我們可以知道,如果t3執行後,喚醒了t1時,t1執行完成後,t1又喚醒t2,如果在喚醒前我們讓線程 重新去判斷標誌位,那就不存在連續生產兩個的情況。 也就是說,把 if() ,修改成 while() 。
//資源類文件
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// 生產函數
public synchronized void setName(String name)
{
while(flag) { //每次執行都判斷標記
try {wait();}catch(Exception e) {}
}
this.name = name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag = true;
this.notify();
}
// 消費函數
public synchronized void out()
{
while(!flag) //每次執行都判斷標記
{
try {wait();}catch(Exception e) {}
}
System.out.println(Thread.currentThread().getName()+" <----消費者---> "+this.name);
flag = false;
this.notify();;
}
}
.
.
運行下看下打印結果,你會發現,全部線程都wait了。
怎麼解決呢?Java在Object類裏面還有個方法notifyAll()方法。喚醒全部。全喚醒後,他們都會循環去判斷標記,只要循環判斷標記,就知道什麼時候該執行,什麼時候該wait。
//資源類文件
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// 生產函數
public synchronized void setName(String name)
{
while(flag) {
try {wait();}catch(Exception e) {}
}
this.name = name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag = true;
this.notifyAll();
}
// 消費函數
public synchronized void out()
{
while(!flag)
{
try {wait();}catch(Exception e) {}
}
System.out.println(Thread.currentThread().getName()+" <----消費者---> "+this.name);
flag = false;
this.notifyAll();;
}
}
.
.
運行結果如下(只截取部分截圖)
總結 :當出現多個生產者和消費者必須
(1) while 循環判斷標記
(2) notifyAll () 既喚醒本方又喚醒對方