執行緒同步中wait()和notify()的使用(用生產者消費者模式解釋)
wait()和notify()是什麼
雖然我們一般在多執行緒中使用wait和notify的方法,但其實它們是不屬於Thread類的,他們是Object類中的方法,我們先來看一下API中的解釋:
1、notify():喚醒在此物件監視器上等待的單個執行緒。
2、notifyAll():喚醒在此物件監視器上等待的所有執行緒。
3、wait():在其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法前,導致當前執行緒等待。
這裡我們可以知道wait和notify的功能分別是等待和喚醒,但這樣的解釋似乎有些籠統,我再具體解釋一下:
wait()和notify()屬於Object基礎類,而Object類是所有類的父類,於是所有類都有這兩種方法。每個物件都有自己的鎖,且對每個物件來說,鎖是唯一的。那麼這個鎖有什麼作用呢?在多執行緒中,執行緒之間會競爭cpu資源,這會影響到執行緒的安全,所以這裡我們要考慮到執行緒的同步問題,實現同步的方法便是對競爭性的資源加鎖,加鎖通過synchronized關鍵字實現。synchroniz(obj){ //想要加鎖的內容 }。只有當執行緒獲得了obj物件的鎖,才能執行synchronize修飾的程式碼,因為鎖的唯一性,在同一時刻只能有一個執行緒可以得到該物件的鎖,這樣就可以避免執行緒安全問題了。wait()和notify()是操作鎖的兩種方法,假定我們用obj物件給執行緒加鎖(物件可以任取,但在多執行緒的同步時,synchronize修飾程式碼塊時都應該去競爭同一物件的鎖,不然就沒有效果了,這點切記),執行obj.wait()可以使得當前物件失去obj的鎖,讓出資源的使用權,進入等待狀態,暫停當前程式碼的執行,此時其他已被喚醒的執行緒中便有一個執行緒可以獲得該資源(當有多個執行緒的時候,誰獲得資源是不一定的)。執行obj.notify()方法可以喚醒在等待佇列中的一個執行緒(沒有優先順序區分),使得此執行緒有機會去競爭這個鎖。同理notifyAll()可以喚醒所有在等待佇列中的執行緒。
生產者消費者模式
生產者消費者模式可以很好地解釋和實現wait()和notify()的作用。顧名思義,生產者的作用就是生產物資,消費者的作用就是對物資進行消費,比如:自然界的植物光合作用為動物提供食物,人類社會的麥當勞的點單與處理訂單等例子都可以詮釋生產者消費者模式。我們這裡拿一個最簡單的例子來為大家解釋,假設有一間倉庫,生產者為倉庫內新增物質,而消費者從倉庫中取出物資,這裡有三種情況:1、倉庫為空,消費者暫停工作,等待生產者為倉庫內新增物資。2、倉庫滿了,生產者暫停工作,等待消費者把物資從倉庫中搬走。3、倉庫內有物資,消費者和生產者同時工作。
wait()和notify()協調生產者和消費者工作的程式設計思路
我們怎樣通過java程式實現呢?生產者和消費者是兩個獨立的執行緒,執行著各自的run方法。在上述第一種情況下,消費者執行obj.notify()方法,通知生產者開始工作了,再執行obj.wait()方法,讓自己進入等待狀態;第二種情況下,生產者執行obj.notify()方法,告訴消費者要開始工作了,再執行obj.wait()方法,讓自己進入等待狀態;第三種情況下,兩者執行緒都處於執行狀態,進行著生產和消費。
程式碼實現
為了更好地觀察現象,我採取的程式設計思路是:建立生產者類和消費者類,讓他們分別繼承執行緒,改寫run方法,主函式內建立執行緒物件並啟動。生產者每次生產100的物資,消費者每次消費50的物資,初始倉庫內物資為1000,達到2000為滿。物資達到2000時生產者停止工作,喚醒消費者工作,待物資減到0,則消費者暫停工作,喚醒生產者開始工作,周而復始。現象就是物資的量在0~2000之間往復變化。
1、main方法
public class ProducerConsumer {
public int weight=1000; //倉庫貨物重量
public static void main(String[] args){
ProducerConsumer move= new ProducerConsumer();
Object warehouse = new Object();//定義倉庫物件,之後為倉庫加鎖
Producer pro = new Producer();//生產者
Consumer con = new Consumer();//消費者
pro.setMove(move);
pro.setwarehouse(warehouse);
con.setMove(move);
con.setwarehouse(warehouse);
con.start();
pro.start();
}
2、生產者類
public class Producer extends Thread {
private Object warehouse;
private ProducerConsumer move;
public synchronized void run() {
while(true)
{
PutIn(warehouse);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void PutIn(Object warehouse) {
// TODO Auto-generated method stub
synchronized (warehouse) {
if(move.weight<1900)
{
move.weight+=100;
System.out.println("倉庫增加物資:"+100);
System.out.println("倉庫現有物資:"+move.weight);
}
else if(move.weight>=1900&&move.weight<2000)
{
System.out.println("倉庫增加物資:"+(2000-move.weight));
move.weight=2000;
System.out.println("倉庫現有物資:"+move.weight);
try {
warehouse.notify();
warehouse.wait();
System.out.println("生產者被喚醒,消費者等待");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void setMove(ProducerConsumer move) {
// TODO Auto-generated method stub
this.move= move;
}
public void setwarehouse(Object warehouse) {
// TODO Auto-generated method stub
this.warehouse=warehouse;
}
}
3、消費者類
public class Consumer extends Thread {
private Object warehouse;
private ProducerConsumer move;
public synchronized void run(){
while(true)
{
outPut(warehouse);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void outPut(Object warehouse){
synchronized (warehouse) {
if(move.weight>50)
{
move.weight-=50;
System.out.println("倉庫減少物資:"+50);
System.out.println("倉庫現有物資:"+move.weight);
}
else if(move.weight>=0&&move.weight<=50)
{
System.out.println("倉庫減少物資:"+move.weight);
move.weight=0;
System.out.println("倉庫現有物資:"+move.weight);
try {
warehouse.notify();
warehouse.wait();
System.out.println("消費者被喚醒,生產者等待");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void setMove(ProducerConsumer move) {
// TODO Auto-generated method stub
this.move=move;
}
public void setwarehouse(Object warehouse) {
// TODO Auto-generated method stub
this.warehouse=warehouse;
}
}
程式執行結果部分截圖: