生產者和消費者問題(四)

勿以惡小而爲之,勿以善小而不爲--------------------------劉備
勸諸君,多行善事積福報,莫作惡

上一章簡單介紹了 同步和死鎖(三),如果沒有看過,請觀看上一章

一. 生產者和消費者問題

生產者和消費者問題,是多線程中很常見的一種問題, 生產者生產產品,消費者消費產品,但同時,如果產品過多時,生產者暫停生產,讓消費者抓緊消費, 如果產品沒有時,消費者不能消費,讓生產者抓緊生產。

生產的產品是一個實體類,如 Info 類, 裏面有 name 和 content 兩種屬性。 注意,生產產品時,這兩個屬性應該是配套的, 消費時,也應該是配套的。

二. 生產者和消費者問題第一版

二.一 消息實體類 Info

很正常的 name 和 content 兩種屬性構成的 pojo.

public class Info {
    private String name;
    private String content;

    public Info(){

    }
    public Info(String name,String content){
        this.name=name;
        this.content=content;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Info{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

二.二 生產者 Productor

public class Productor implements  Runnable {

    private Info info;

    //傳進來對象
    public Productor(Info info){
        this.info=info;
    }

    private boolean isFirst=true;
    @Override
    public void run() {

        for(int i=0;i<50;i++){

            if(isFirst){  //是第一個,那麼就生產第一個消息

               this.info.setName("兩個蝴蝶飛");
                //生產消息,需要時間
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                this.info.setContent("這是兩個蝴蝶飛");

                isFirst=false;

            }else{

                this.info.setName("老蝴蝶");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                this.info.setContent("這是老蝴蝶");
                isFirst=true;
            }

        }

    }
}

二.三 消費者 Consumer

public class Consumer implements Runnable {

    private Info info;

    public Consumer(Info info){
        this.info=info;
    }
    @Override
    public void run() {
        for(int i=0;i<50;i++){

            //消費也需要時間
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(info.toString());
        }
    }
}

二.四 主程序測試 Demo

public class Demo {
    public static void main(String[] args) {
        //定義產品對象
        Info info=new Info();

        //生產 和消費用同一個對象 
        Productor productor=new Productor(info);
        //消費
        Consumer consumer=new Consumer(info);


        Thread thread=new Thread(productor);

        Thread thread1=new Thread(consumer);

        //啓動

        thread.start();

        thread1.start();

    }
}

二.五 測試運行,發現問題

在這裏插入圖片描述
發現,取出的產品竟然亂套了。 這是沒有同步的原因。

在設置內容時,需要進行同步, 在取出來內容時,也需要同步。

同步,需要放置在 Info 對象中進行處理。

三. 生產者和消費者同步 第二版

將上面的程序進行改寫。

三.一 同步產品類 Info

裏面有兩個同步的方法。

public class Info {
    private String name;
    private String content;

    public Info(){

    }
    //設置產品
    public synchronized  void setInfo(String name,String content){

        this.setName(name);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.setContent(content);
    }

    //取出產品
    public synchronized  void getInfo(){
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(this.getName()+"---->"+this.getContent());
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return this.content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

三.二 生產者 Productor

public class Productor implements  Runnable {

    private Info info=null;

   private boolean isFirst=true;
    public Productor(Info info){
        this.info=info;
    }


    @Override
    public void run() {



        for(int i=0;i<50;i++){

            System.out.println("輸出 i:"+i+",isFirst:"+isFirst);
            if(isFirst){  //是第一個,那麼就生產第一個消息
                this.info.setInfo("兩個蝴蝶飛","這是兩個蝴蝶飛");

                isFirst=false;

            }else{
                this.info.setInfo("老蝴蝶","這是老蝴蝶");

                isFirst=true;
            }

        }

    }
}

三.三 消費者 Consumer

public class Consumer implements Runnable {

    private Info info=null;

    public Consumer(Info info){
        this.info=info;
    }
    @Override
    public void run() {
        for(int i=0;i<50;i++){
            System.out.println("");
            //獲取信息
            this.info.getInfo();
        }
    }
}

三.四 主程序測試Demo

public class Demo {
    public static void main(String[] args) {
        //定義生產者

        Info info=new Info();

        //生產
        Productor productor=new Productor(info);

        //消費
        Consumer consumer=new Consumer(info);

        Thread thread=new Thread(productor);

        Thread thread1=new Thread(consumer);

        //啓動

        thread.start();

        thread1.start();

    }
}

三.五 測試運行,發現問題

有圖片

常常會先生產這一部分,後生產那一部分, 並不是生產一個,拿一個。

需要用 Object 對象的 wait() 和 notify(), notifyAll() 方法進行設置。

四. 等待和喚醒 生產者和消費者第三版

在Info 對象裏面設置一個標識位, 如果生產了一件產品,那麼就生產者先等待,讓消費者去消費好之後,再重新生產,避免多次生產一件產品。

四.一 等待和喚醒 Info

package com.yjl.thread.cp.c3;

/**
 * package: com.yjl.thread.cp
 * className: Info
 * Description: 請輸入相應的描述
 *
 * @author : yuezl
 * @Date :2020/6/13 12:53
 */
public class Info {
    private String name;
    private String content;


    //定義標識位
    private boolean flag=false;

    public Info(){

    }
    public synchronized  void setInfo(String name,String content){


        if(flag){  //爲真

            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.setName(name);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.setContent(content);

        flag=true;

        //當前只有一個線程,喚醒一個線程, 也可以直接喚醒多個。
        super.notifyAll();
    }

    public synchronized  void getInfo(){

        if(!flag){
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(this.getName()+"---->"+this.getContent());

        flag=false;

        //當前只有一個線程,喚醒一個線程, 也可以直接喚醒多個
        super.notifyAll();
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return this.content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

生產者,消費者和 Demo 均與 第二版的內容一樣。

運行程序,控制檯打印輸出

有圖片

發現,可以做到每次生產一個產品,取出一個產品的功能了。

但是,這種情況下,只有一個生產者和一個消費者,如果都有兩個呢?

有圖片

其實,我們可以先創建一個倉庫, 當生產時,可以將產品放置到倉庫裏面, 當倉庫滿時,纔不讓繼續生產, 如果倉庫沒有滿,就繼續生產。 當倉庫沒有產品時,就不讓繼續消費了。

五. 倉庫版 生產者和消費者 第四版

五.一 產品 Info 類

Info 就不需要進行等待和喚醒了。

public class Info {
    private String name;
    private String content;


    public Info(){

    }
    public synchronized  void setInfo(String name,String content){

        this.setName(name);
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.setContent(content);
    }

    public synchronized  void getInfo(){
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(this.getName()+"---->"+this.getContent());
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return this.content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Info{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

五.二 倉庫 Storage

public class Storage {

    private static final int DEFAULT_SIZE=10;

    //定義一個集合,用於存儲對象信息。
    private volatile List<Info> infoList=new ArrayList<Info>();

    private  volatile int count=0;
    /**
     * 進行生產
     */
    public  void produce(Info info){

       synchronized (infoList) {

            //超過出容易
            if(infoList.size()>=DEFAULT_SIZE){
                System.out.println("生產者:"+Thread.currentThread().getName()+"的物品不能放置進來,因爲倉庫已經滿了,");
                try {

                    infoList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println();
           count++;
            System.out.println("生產者:"+Thread.currentThread().getName()+"生產了一件產品:"
            +info.toString()+",已經放置在倉庫了,倉庫有:"+count+"件商品");
            infoList.add(info);

            infoList.notifyAll();
        }
    }

    /**
     *
     */
    public synchronized  Info consume(){

        synchronized (infoList){

            //爲 0了
            if(infoList.size()<=0){
                System.out.println("消費者:"+Thread.currentThread().getName()+"不能消費產品,因爲倉庫已經空了.");
                 try{
                     infoList.wait();
                 }catch(InterruptedException e){
                     e.printStackTrace();
                 }
            }

            Info lastInfo=infoList.get(count-1);
            infoList.remove(count-1);

            count--;

            System.out.println("消費者:"+Thread.currentThread().getName()+"消費了一件產品:"+lastInfo.toString()
            +",還剩下"+count+"件產品");
            //通知

            infoList.notifyAll();

            return lastInfo;
        }
    }

    //public static List<Info> getInfoList() {
     //   return infoList;
   // }
}

五.三 生產者 Productor

public class Productor implements  Runnable {

    private Storage storage;

    private boolean isFirst=true;

    //傳入倉庫
    public Productor(Storage storage){

        this.storage=storage;
    }


    @Override
    public void run() {
        for(int i=0;i<50;i++){
            //定義產品
            Info info=new Info();
           System.out.println("生產者運行i:"+i);

            if(isFirst){  //是第一個,那麼就生產第一個消息
                info.setInfo("兩個蝴蝶飛","這是兩個蝴蝶飛");


                isFirst=false;

            }else{

                info.setInfo("老蝴蝶","這是老蝴蝶");


                isFirst=true;
            }
            //放置到倉庫裏面
            storage.produce(info);

        }

    }
}

五.四 消費者 Consumer

public class Consumer implements Runnable {

    private Storage storage;

    public Consumer(Storage storage){

        this.storage=storage;
    }
    @Override
    public void run() {
        for(int i=0;i<50;i++){

           System.out.println("消費者運行i:"+i);
            //獲取信息
            Info info=storage.consume();

            //info.getInfo();
        }
    }
}

五.五 主程序測試 Demo

public class Demo {
    public static void main(String[] args) {
        //定義生產者

        Storage storage=new Storage();
        //生產
        Productor productor=new Productor(storage);

        //消費
        Consumer consumer=new Consumer(storage);

        Thread thread=new Thread(productor);

        Thread thread1=new Thread(productor);

        Thread thread2=new Thread(consumer);

        Thread thread3=new Thread(consumer);

        //啓動

        thread.start();

        thread1.start();

        thread2.start();

        thread3.start();

    }
}

五.六 測試運行,發現問題

有圖片

發現會生產一件,消費一件, 這是兩個生產者和兩個消費者。

如果是三個生產者和一個消費者呢?

public class Demo {
    public static void main(String[] args) {
        //定義生產者

        Storage storage=new Storage();
        //生產
        Productor productor=new Productor(storage);

        //消費
        Consumer consumer=new Consumer(storage);

        Thread thread=new Thread(productor);

        Thread thread1=new Thread(productor);

        Thread thread2=new Thread(consumer);

        Thread thread3=new Thread(consumer);

        //啓動

        thread.start();

        thread1.start();
        new Thread(productor).start();

        thread2.start();

       // thread3.start();

    }
}

運行程序,查看

有圖片

倉庫滿了之後,就不能再繼續生產產品了。

如果一個生產者,三個消費者呢?

public class Demo {
    public static void main(String[] args) {
        //定義生產者

        Storage storage=new Storage();
        //生產
        Productor productor=new Productor(storage);

        //消費
        Consumer consumer=new Consumer(storage);

        Thread thread=new Thread(productor);

        Thread thread1=new Thread(productor);

        Thread thread2=new Thread(consumer);

        Thread thread3=new Thread(consumer);

        //啓動

        thread.start();

       // thread1.start();


        thread2.start();

        thread3.start();

        new Thread(consumer).start();

    }
}

運行程序,

有圖片

消費者不能取出了,會等待,等待生產者往倉庫裏面放置產品。

生產者和消費者的問題特別重要,一定要掌握。


謝謝您的觀看,如果喜歡,請關注我,再次感謝 !!!

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