生產-消費者模型

介紹生產消費者模型中所要用到的幾個方法:


wait() 方法

public final void wait()throws InterruptedException;

  • 當調用wait()方法後,線程會立刻停止,並將該線程從鎖對象的同步隊列提取到等待隊列(WAIT)中,直到下次被notify()方法喚醒,或者被中斷爲止。
  • wait()方法只能在同步代碼塊或同步方法中使用,必須爲synchronized(內建鎖)。
  • wait()方法調用之後會立刻將鎖釋放,然後其餘的線程重新競爭鎖資源。

關於wait()的同名方法:

  • 超時等待(單位:毫秒)  wait(long timeout):該方法不會像wait()一樣去死等,它是會在wait一段時間後,若未被喚醒,則線程會直接退出;
  • 超時等待(單位:毫秒,納秒)  wait(long timeout,int nanos):該方法只不過是在上面的基礎上添加了納秒控制(感覺並沒有什麼卵用);

notify() 方法

  • 喚醒處於等待的線程
  • wait()方法只能在同步代碼塊或同步方法中使用,從等待對列中隨機挑選一個線程放回到同步隊列中取競爭鎖;注意如果有多個線程在等待隊列時,它會隨機挑選一個線程放到同步隊列中去,但是這裏的隨機是概念性的,在不同的編譯器下它挑選的順序是不太一樣的,有的可能從等待隊列的開始拿取,有的可能是從最後拿取。
  • notify()方法在調用之後並不會立即的釋放鎖,它會將當前的線程執行完之後再釋放鎖。

關於notify()的同名方法:

  • notifyAll():喚醒所有處於等待隊列中的線程

關於鎖對象中的同步隊列和等待隊列:

一:同步隊列,存儲的是獲取鎖失敗的線程(該隊列中的線程都在不斷地嘗試競爭獲取鎖)

二:等待隊列,存儲的是調用wait()方法後的線程。


線程由運行態 -- 阻塞(wait)的幾種方式

  • 調用wait()方法
  • 調用sleep()方法,線程立刻交出CPU,只是不會釋放鎖
  • 調用suspend()方法,將線程掛起
  • 線程獲取鎖失敗自行進入到阻塞狀態

關於生產-消費者模型的具體實現

首先我們要搞清楚生產-消費者之間的關係,當生產者要生產一個商品時,消費者要去消費這個商品,因此這裏就可以將兩者之間的關係找出來,那就是Goods商品,他們之間都需要Goods來維護兩者之間的關係,所以我們就可以使用Goods這個商品類作爲第三方解耦;下面就具體的實現

 

單生產者單消費者模型

package www.Dyson;
//定義一個商品類,用於解耦
class Goods{
    private String goodsName;  //定義商品名稱
    private int goodsNums=0;   //定義商品庫存
    //生產商品方法
    public synchronized void setGoodsName(String goodsName){
        this.goodsName=goodsName;
        //當庫存大於0時證明有商品,這時需要等待消費者消費商品
        if(goodsNums>0){
            System.out.println("等到消費者消費!");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsNums++;
        System.out.println("生產"+toString());
        //當生產者生產完商品後,需要喚醒處於等待隊列的方法去繼續消費商品
        notify();
    }
    //消費商品方法
    public synchronized void getGoods(){
        //當庫存小於1時,證明沒有商品,這時需要等待生產者生產商品
        if(this.goodsNums<1){
            System.out.println("等待生產者生產!");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        --goodsNums;
        System.out.println("消費"+toString());
        //當消費者消費完商品後需要喚醒處於等待隊列的方法去繼續生產商品
        notify();
    }
    //打印商品名稱以及商品庫存
    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", goodsNums=" + goodsNums +
                '}';
    }
}
//生產者類
class produce implements Runnable{
    private Goods goods;
    public produce(Goods goods){
        this.goods=goods;
    }
    @Override
    public void run() {
        this.goods.setGoodsName("一致紀梵希口紅!小羊皮");
    }
}
//消費者類
class consum implements Runnable{
    private Goods goods;
    public consum(Goods goods){
        this.goods=goods;
    }
    @Override
    public void run() {
        this.goods.getGoods();
    }
}
public class procon {
    public static void main(String[] args) {
        Goods goods=new Goods();
        produce pro=new produce(goods);
        consum con=new consum(goods);
        Thread th1=new Thread(pro);
        Thread th2=new Thread(con);      
        th1.start();
        th2.start();
    }
}

多生產者多消費者模型

這裏需要將上面的代碼改三個地方:

  • 將生產者類和消費者類中的run()方法改爲死循環,讓其一直去生產和消費
  • 將Goods裏邊的消費方法和生產方法的wait()使用判斷語句改爲While循環。

雖然不將if改成while不會造成死鎖,但是會造成如下情況,原因如下圖分析  

                       

  • 將notify()改爲notifyAll()將等待隊列中的所有線程喚醒去競爭鎖。如果使用notify()化可能造成死鎖的情況,比如所:當你有幾十個生產線程,而卻只有一個消費線程時,當一個生產者去執行完庫存加一操作後,從等待隊列中隨機喚醒了一個生產線程時,然後該線程就會判斷庫存是否大於1,結果發現大於1,該線程就會再次進入到等待隊列中取。當程序走了一遍上面的流程之後就會將所有的線程都放到等待隊列中去,從而造成死鎖情況
package www.Dyson;

import java.util.ArrayList;

//定義一個商品類,用於解耦
class Goods{
    private String goodsName;  //定義商品名稱
    private int goodsNums=0;   //定義商品庫存
    //生產商品方法
    public synchronized void setGoodsName(String goodsName){
        this.goodsName=goodsName;
        //當庫存大於0時證明有商品,這時需要等待消費者消費商品
        //修改二
        while(goodsNums>0){
            System.out.println("等到消費者消費!");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsNums++;
        System.out.println("生產"+toString());
        //當生產者生產完商品後,需要喚醒處於等待隊列的方法去繼續消費商品
        //修改三
        notifyAll();
    }
    //消費商品方法
    public synchronized void getGoods(){
        //當庫存小於1時,證明沒有商品,這時需要等待生產者生產商品
        while (this.goodsNums<1){
            System.out.println("等待生產者生產!");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        --goodsNums;
        System.out.println("消費"+toString());
        //當消費者消費完商品後需要喚醒處於等待隊列的方法去繼續生產商品
        notifyAll();
    }
    //打印商品名稱以及商品庫存
    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", goodsNums=" + goodsNums +
                '}';
    }
}
//生產者類
class produce implements Runnable{
    private Goods goods;
    public produce(Goods goods){
        this.goods=goods;
    }
    @Override
    public void run() {
        while(true){
            this.goods.setGoodsName("一致紀梵希口紅!小羊皮");
        }
    }
}
//消費者類
class consum implements Runnable{
    private Goods goods;
    public consum(Goods goods){
        this.goods=goods;
    }
    @Override
    public void run() {
        //修改一
        while(true){
            this.goods.getGoods();
        }
    }
}
public class procon {
    public static void main(String[] args) {
        Goods goods=new Goods();
        ArrayList<Thread> a=new ArrayList<>();
        for(int i=0;i<10;i++){
            a.add(new Thread(new produce(goods)));
        }
        for(int i=0;i<5;i++){
            a.add(new Thread(new consum(goods)));
        }
        for(Thread tmp:a){
            tmp.start();
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章