生產者和消費者模型
線程通信:不同的線程執行不同的任務,如果這些任務有某種關系,各個線程必須要能夠通信,從而完成工作。線程通信中的經典問題:生產者和消費者問題
模型:

這個模型也體現了面向對象的設計理念:低耦合
也就是為什么生產者生產的東西為什么不直接給消費者,還有經過一個緩沖區(共享資源區)
這就相當於去包子店吃包子,你要5個包子,老板把5個人包子放在一個盤子中再給你,這個盤子就是一個緩沖區。
現在模擬一個生產者和消費者模型
-
生產者生產“李小龍 男”,消費者消費也就是打印出“李小龍 男”
-
生產者生產“狗晗 女”,消費者消費也就是打印出“狗晗 女”
-
要求生產者生產一個,消費者消費一個。也就是緩沖區為1
-
期望結果:
李小龍 男
狗晗 女
李小龍 男
狗晗 女
···
解決問題
需要創建4個類,分別是生產者,消費者,共享資源類,測試類
-
生產者類實現Runnable接口,覆蓋run方法
public class Producer implements Runnable{ ShareResource resource = null; public Producer(ShareResource resource){ this.resource = resource; } @Override public void run() { for(int i = 0; i < 50;i++){ if(i % 2 == 0){ resource.product("李小龍", "男"); }else{ resource.product("狗晗","女"); } } } } -
消費者類實現Runnable接口,覆蓋run方法
public class Consumer implements Runnable { ShareResource resource = null; public Consumer(ShareResource resource){ this.resource = resource; } public void run(){ for(int i = 0;i < 50;i++){ resource.consume(); } } } -
共享資源類有生產者和消費者兩個類的引用
public class ShareResource { private String name; private String sex; public void product(String name,String sex){ this.name = name; try{ Thread.sleep(10); }catch(Exception e){ e.printStackTrace(); } this.sex = sex; } public void consume(){ try{ Thread.sleep(10); }catch(Exception e){ e.printStackTrace(); } System.out.println(this.name + " " + this.sex); } } -
測試類可以啟動線程
public class Demo { public static void main(String[]args){ ShareResource resource = new ShareResource(); new Thread(new Producer(resource)).start(); new Thread(new Consumer(resource)).start(); } }
運行后發現,結果性別發生了紊亂:

我靠,這我龍哥願意嗎,怎么回事?
就是因為多線程並發同一資源,要把它們的方法用synchronized修飾保證它們生產和消費不同時進行,當你加上synchronized之后:

確實性別紊亂的問題是解決了,狗晗是高興了,但是它們並不是交替出現的啊,怎么回事?
你要交替出現,必須一個等着一個啊,你生產完了,你得停下來休息啊,讓消費者先消費,等到消費者消費完之后,讓消費者把你叫醒再繼續生產,消費者就又去休息去了,這個消費者優點類似於吃了睡,睡了在吃一樣。
wait和notify方法
-
wait方法:執行該方法的對象釋放同步鎖,JVM把該線程存放到等待池中,等待其他線程喚醒
-
notify方法:喚醒在等待池中的等待的任意一個線程,把線程轉移到鎖池中
-
notifyAll方法:喚醒在等待池中等待的所有線程,把線程轉移到鎖池中
等待池:沒有機會獲取同步鎖,只能等待被喚醒,被轉移到鎖池中。
鎖池: 當前生產者在生產數據的時候(先擁有同步鎖),其他線程就在鎖池中等待獲取鎖.,當線程執行完同步代碼塊的時候,就會釋放同步鎖,其他線程開始搶鎖的使用權.
又來看這個模型,應該得定義一個判斷當前共享資源區是否為空,如果為空,消費者就應該等待生產者生產,當生產者生產 完畢后應該喚醒消費者進行消費。如果不為空,生產者就應該等待消費者,等消費者完畢后再喚醒生產者繼續生產
public class ShareResource {
private String name;
private String sex;
private boolean isEmpty = true;//判斷共享區空和滿的一個標志
//生產者生產
synchronized public void product(String name,String sex){
try {
while(!isEmpty){//共享區不空,生產者需要停下等待消費者消費
/**
* this是同步鎖(同步監聽對象),wait方法是Object類中 的方法,調用該方法就釋放同步鎖
* 然后JVM把該線程存放到等待池中,等待其他線程喚醒該線程
* 該方法只能被同步監聽對象調用,否則報錯IllegalMonitorStateException.
*/
this.wait();
}
//----------開始生產-----------
this.name = name;
Thread.sleep(10);
this.sex = sex;
//---------結束生產------------
isEmpty = false; //設置共享區為不空
/**
* notify方法是喚醒等待池中的隨機一個線程,把線程轉移到鎖池中等待(鎖池中有機會獲取到鎖)
* notifyAll方法是喚醒等待池中的所有線程,把線程轉移到鎖池中等待
* 該方法只能被同步監聽對象鎖調用,否則報錯
*/
this.notify(); //喚醒一個消費者 notify是喚醒其中一個,notifyAll是喚醒全部
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消費者消費
synchronized public void consume(){
try {
while(isEmpty){//當共享區為空時,消費者進行等待
this.wait();
}
//------------開始消費-------------
Thread.sleep(30);
System.out.println(name + " " + sex);
//------------結束消費-------------
isEmpty = true;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用synchronized修飾方法,會自動獲取鎖,自動釋放鎖。從java5開始使用Lock機制取代synchronized代碼塊和synchronized方法,使用Condition接口對象的await,signal,signalAll方法取代notify,notifyAll,用法還是大同小異
public class ShareResource {
private String name;
private String sex;
private boolean isEmpty = true;
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void product(String name,String sex){
lock.lock();//獲取鎖
try {
while(!isEmpty){//不空,生產者要等待消費者消費
condition.await();//等待,相當於this.wait();
}
this.name = name;
Thread.sleep(10);
this.sex = sex;
isEmpty = false;//把共享區置為不空
condition.signal();//喚醒一個線程,相當於this.notify(). signalAll 可以喚醒全部線程
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//釋放鎖
}
}
public void consume(){
lock.lock();
try {
while(isEmpty){//共享區為空,需要等待生產者生產
condition.await();
}
Thread.sleep(20);
System.out.println(this.name + " " + this.sex);
isEmpty = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
結果就是:

Lock機制
創建一個Lock接口實現類ReetranLock的對象,進入同步方法后立即加鎖,lock.lock();最后釋放鎖,看API中的典型代碼

