Java多線程:生產者與消費者案例

前言

想象一下生活中哪些是和線程沾邊的?飯店炒菜就是一個很好的例子

首先客人要吃菜,前提是廚師要炒好,也就是說,廚師不炒好的話客人是沒有飯菜的。這時候,廚師就是一個線程,客人拿菜就是另一個線程。

工具

jdk13,IDEA2019.1.4

知識點

Thread、Runnable、synchronized、面向對象知識(繼承、封裝、接口、方法重寫)、條件判斷以及線程的一些其他知識點

設計思路

首先要有兩個線程,也就是說要兩個類,分別是Producer(生產者)和Consumer(消費者)。

由於我們是模擬廚師與客人之間的互動,也就是說還需要一個類來封裝信息:Message(類)。

然後,避免線程之間發生數據混亂的情況,肯定還需要使用synchronized來進行線程同步。

具體步驟

首先我們來一份沒有用synchronized的代碼,先看看效果:

public class Message {
    private String title;
    private String content;
    Message(){
    };
    public Message(String title, String content) {
        this.title = title;
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}
/*
* 定義生產者類Producer
* */
class Producer implements Runnable{
    private Message msg=null;
    public Producer(Message msg) {
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.setTitle("喜歡夜雨嗎?");
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    System.out.println(e);
                }
                this.msg.setContent("是的呢!");
            }else {
                this.msg.setTitle("還不關註我嗎?");
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    System.out.println(e);
                }
                this.msg.setContent("好的呢!");
            }
        }
    }
}
/*
* 定義消費者類Consumer
* */
class Consumer implements Runnable{
    private Message msg=null;
    public Consumer(Message msg) {
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                System.out.println(e);
            }
            System.out.println(this.msg.getTitle()+"--->"+this.msg.getContent());
        }
    }
}
class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}

看看運行結果:

在這裡插入圖片描述

看仔細咯,發生瞭數據錯亂啊,Title與Content沒有一一對應欸。咋辦?

能咋辦,改代碼唄。

發生數據混亂的原因完全是因為,生產者線程還沒生產的時候,消費者就已經消費瞭(至於消費的啥我也不知道,我也不敢問啊)。所以造成瞭數據混亂,不過我們上面說瞭啊,要使用synchronized來讓線程同步一下。但是又會出問題,我們接著往下看

class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}
class Message{
    private String title,content;
    public synchronized void set(String title,String content){
        this.title=title;
        this.content=content;
    }
    public synchronized void get(){
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        System.out.println(this.title+"-->"+this.content);
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
class Producer implements Runnable{
    private Message msg=null;
    Producer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.set("喜歡夜雨嗎?","是的呢!");
            }else {
                this.msg.set("還不關註嗎?","好的呢!");
            }
        }
    }
}
class Consumer implements Runnable{
    private Message msg=null;
    Consumer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            this.msg.get();
        }
    }
}

我又重新封裝瞭一些方法,然後運行的時候,wc,數據倒是不混亂瞭,但是呢,數據重復瞭一大堆。

在這裡插入圖片描述

為啥會出現這個問題呢?最後想瞭一下,會出現這種問題的,就是因為線程的執行順序的問題。我們想要實現的效果是生產者線程執行瞭之後,讓生產者線程等待,而後讓消費者線程執行,等待消費者線程執行完成之後就又讓生產者線程繼續執行。後來我又查瞭查官方文檔,發現Object類中專門有三個方法是與線程相關的:

方法 描述
public final void wait() throws InterruptedException 線程的等待
public final void notify() 喚醒第一個等待線程
public final void notifyAll()

當我看到這些方法的時候,心裡愣瞭一下,這不就是我們想要的方法嗎,用wait()方法來讓生產者線程等待,然後運行消費者線程,等消費者線程執行完瞭之後又讓生產者線程去執行。這其中我們用true和false來表示運行開始和運行暫停。

最後我們來看看完成品的代碼:

class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}
class Message extends Exception{
    private String title,content;
    private boolean flag=true;
    // true表示正在生產,不要來取走,因為沒由讓消費者區走的
    // false表示可以取走,但是不能生產
    public synchronized void set(String title,String content){
        if (this.flag==false){
            try {
                super.wait();
            }catch (InterruptedException e){
                System.out.println(e);
            }
        }
        this.title=title;
        try {
            Thread.sleep(60);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        this.content=content;
        this.flag=true; // 生產完成,修改標志位
        super.notify(); // 喚醒等待線程
    }
    public synchronized void get(){
        if (flag==true) {// 已經生產好瞭,等待取走
            try {
                super.wait();
            }catch (InterruptedException e){
                System.out.println(e);
            }
        }
        try {
            Thread.sleep(60);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        System.out.println(this.title+"-->"+this.content);
        this.flag=true;
        super.notify();
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
class Producer implements Runnable{
    private Message msg=null;
    Producer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.set("喜歡夜雨嗎?","是的呢!");
            }else {
                this.msg.set("還不關註嗎?","好的呢!");
            }
        }
    }
}
class Consumer implements Runnable{
    private Message msg=null;
    Consumer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            this.msg.get();
        }
    }
}

運行結果我就不貼瞭,你們自己去測試一下吧…

總結

這個案例完美的呈現瞭線程以及面向對象知識的綜合運用,具有很強的實際操作性

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: