生產者消費者問題-代碼詳解(Java多線程)

你好我是辰兮,很高興你能來閱讀,本篇是整理了Java多線程中常見的生產者消費者問題,也是面試手寫代碼的高頻問題,分享獲取新知,大家共同進步!



一、生產者消費者問題

生產者消費者問題(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。

該問題描述了兩個共享固定大小緩衝區的線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。

在這裏插入圖片描述
生產者的主要作用是生成一定量的數據放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入數據,消費者也不會在緩衝區中空時消耗數據。


生產消費者模型

生產者消費者模型具體來講,就是在一個系統中,存在生產者和消費者兩種角色,他們通過內存緩衝區進行通信,生產者生產消費者需要的資料,消費者把資料做成產品。生產消費者模式如下圖。

在這裏插入圖片描述


二、代碼實現

案例如下

在這裏插入圖片描述
 首先定義一個類 ,這個類創建一個長度爲5的數組,然後用synchronized同步鎖實現生產數據和消費數據兩個方法,兩個方法中分別有 wait()和notify();

public class Resources {

    private int[] arr = new int[5];
    private int count = 0;

    /**
     * 生產一個數據
     */
    synchronized public void product() throws Exception {
        if(count == arr.length) {
            wait();
        }else {
            int m =  (int)Math.floor(Math.random()*10+1) ;
            arr[count] = m;
            count ++ ;
            System.out.println("生產:"+m);
            //喚醒消費者
            notify();
        }
    }

    /**
     * 消費一個數據
     */
    synchronized public void consume() throws Exception {
        if(count == 0) {
            wait();
        }else {
            int n = arr[count-1];
            arr[count-1] = 0;
            count--;
            System.out.println("消費:"+n);
            //喚醒生產者
            notify();
        }
    }
}

然後創建生產者線程,引入剛剛的對象,用剛剛的對象調用生產的方法

public class ProduceThread extends Thread {
    private Resources r;
    public ProduceThread(Resources r) {
        this.r = r;
    }
    public void run() {
        try {
            while(true) {
                r.product();
                Thread.sleep(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然後創建消費者線程,同樣引入Resources對象,然後再用這個對象調用消費數據的方法

public class ConsumerThread extends Thread {
    private Resources r;
    public ConsumerThread(Resources r) {
        this.r = r;
    }
    public void run() {
        try {
            while(true) {
                r.consume();
                Thread.sleep(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

測試類開始測試

public class Test {

    public static void main(String[] args) {

        Resources r = new Resources();

        ProduceThread t1 = new ProduceThread(r);
        ConsumerThread t2 = new ConsumerThread(r);

        t1.start();
        t2.start();
    }

}

在main方法中,開啓消費者和生產者線程。因爲生產者和消費者用的是同一個對象的不同synchronized方法,所以兩個線程會產生producterAndConsumer 對象鎖的競爭

生產:4
消費:4
生產:7
消費:7
生產:8
生產:4
消費:4
消費:8
生產:4
消費:4
生產:2
消費:2
生產:8
生產:5
消費:5
消費:8
生產:3
生產:1
.....//無休止

三、拓展知識

1、爲什麼wait, notify 和 notifyAll這些方法不在thread類裏面?

①JAVA提供的鎖是對象級的而不是線程級的(如我例子中的list,list是對象),每個對象都有鎖,通過線程獲得。

②如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。

③簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因爲鎖屬於對象。


補充:如何理解Java 中每個對象都有個鎖?

在Java語言中,每一個對象有一把鎖。線程可以使用synchronized關鍵字來獲取對象上的鎖。

java允許多線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據不準確,相互之間產生衝突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,從而保證了該變量的唯一性和準確性。


2、java中notify 和 notifyAll有什麼區別?

notify()notifyAll()的共同點:

均能喚醒正在等待的線程,並且均是最後只有一個線程獲取資源對象的鎖。

notify()notifyAll()的不同點:

notify() 只能喚醒一個線程,而notifyall()能夠喚醒所有的線程,當線程被喚醒以後所有被喚醒的線程競爭獲取資源對象的鎖,其中只有一個能夠得到對象鎖,執行代碼。注意:wait()方法並不是在等待資源的鎖,而是在等待被喚醒。


3、未完待續.


The best investment is to invest in yourself

在這裏插入圖片描述

2020.06.13 記錄辰兮的第81篇博客

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