並發王者課-青銅10:千錘百煉-如何解決生產者與消費者經典問題


歡迎來到《並發王者課》,本文是該系列文章中的第10篇

在本篇文章中,我將為你介紹並發中的經典問題-生產者與消費者問題,並基於前面系列文章的知識點,通過wait、notify實現這一問題的簡版方案

一、生產者與消費者問題

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

這個問題描述了共享固定大小緩沖區的兩個進程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是生成一定量的數據放到緩沖區中,然后重復此過程。與此同時,消費者也在緩沖區消耗這些數據。

生產者與消費者問題的關鍵在於要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據

要解決該問題,就必須讓生產者在緩沖區滿時休眠(要么干脆就放棄數據),等到下次消費者消耗緩沖區中的數據的時候,生產者才能被喚醒,開始往緩沖區添加數據

同樣,也可以讓消費者在緩沖區空時進入休眠,等到生產者往緩沖區添加數據之后,再喚醒消費者。通常采用線程間通信的方法解決該問題,常用的方法有信號量等。如果解決方法不夠完善,則容易出現死鎖的情況。出現死鎖時,兩個線程都會陷入休眠,等待對方喚醒自己。

當然,生產者與消費者問題並不是局限於單個生產者與消費者,在實際工作中,遇到更多的是多個生產者和消費者的情形

生產者與消費者模式在軟件開發與設計中有着非常廣泛的應用。在這一模式中,生產者與消費者相互獨立,它們僅通過緩沖區傳遞數據,因此可以用於程序間的解耦異步削峰等。

生產者與消費者問題的要點:

  • 生產者與消費者解耦,兩者通過緩沖區傳遞數據
  • 緩沖區數據裝滿了之后,生產者停止數據生產或丟棄數據
  • 緩沖區數據為空后,消費者停止消費並進入等待狀態,等待生產者通知

二、實現生產者與消費者方案

本節中,我們通過王者中的一個場景來模擬生產者與消費者問題。

在王者中,英雄蘭陵王需要通過打野來發育,但是野區的野怪在被打完之后,需要隔一段時間再投放。

所以,我們創建兩個線程,一個作為生產者向野區投放野怪,一個作為消費者打怪。

生產者:每秒檢查一次野區,如果野區沒有野怪,則進行投放。野怪投放后,通知打野英雄

// 野怪投放【生產者】
public static class WildMonsterProducer implements Runnable {
  public void run() {
    try {
      createWildMonster();
    } catch (InterruptedException e) {
      System.out.println("野怪投放被中斷");
    }
  }

  //投放野怪,每1秒檢查一次
  public void createWildMonster() throws InterruptedException {
    for (int i = 0;; i++) {
      synchronized(wildMonsterArea) {
        if (wildMonsterArea.size() == 0) {
          wildMonsterArea.add("野怪" + i);
          System.out.println(wildMonsterArea.getLast());
          wildMonsterArea.notify();
        }
      }
      Thread.sleep(1000);
    }
  }
}

消費者:打野英雄蘭陵王作為消費者,在野區打怪發育。如果野區有野怪,則打掉野怪。 如果沒有,會進行等待野區新的野怪產生

// 蘭陵王,打野英雄
public static class LanLingWang implements Runnable {
  public void run() {
    try {
      attackWildMonster();
    } catch (InterruptedException e) {
      System.out.println("蘭陵王打野被中斷");
    }
  }

  // 打野,如果沒有則進行等待
  public void attackWildMonster() throws InterruptedException {
    while (true) {
      synchronized(wildMonsterArea) {
        if (wildMonsterArea.size() == 0) {
          wildMonsterArea.wait();
        }
        String wildMonster = wildMonsterArea.getLast();
        wildMonsterArea.remove(wildMonster);
        System.out.println("收獲野怪:" + wildMonster);
      }
    }
  }
}

創建野區,並啟動生產者與消費者線程。

public class ProducerConsumerProblemDemo {

    // 野怪活動的野區
    private static final LinkedList<String> wildMonsterArea = new LinkedList<String>();

    public static void main(String[] args) {
        Thread wildMonsterProducerThread = new Thread(new WildMonsterProducer());
        Thread lanLingWangThread = new Thread(new LanLingWang());

        wildMonsterProducerThread.start();
        lanLingWangThread.start();
    }
}

在上面幾段代碼中,你需要重點注意的是synchronizedwaitnotify用法,它們是本次方案的關鍵。運行結果如下:

野怪0
收獲野怪:野怪0
野怪1
收獲野怪:野怪1
野怪2
收獲野怪:野怪2
野怪3
收獲野怪:野怪3
野怪4
收獲野怪:野怪4
野怪5
收獲野怪:野怪5
野怪6
收獲野怪:野怪6

從結果可以看到,生產者在創建野怪后,打野英雄蘭陵王會進行打野,實現了生產者與消費者的問題。

小結

以上就是關於線程異常處理的全部內容,在本文中我們基於waitnotify來解決生產者與消費者問題。對於本文內容,你需要理解生產者與消費者問題的核心是什么。另外,本文所提供的方案僅僅是這一問題多種解決方案中的一種,在后面的文章中,我們會根據新的知識點提供其他的解法。

正文到此結束,恭喜你又上了一顆星✨

夫子的試煉

  • 編寫代碼實現生產者與消費者問題。

延伸閱讀

關於作者

關注公眾號【庸人技術笑談】,獲取及時文章更新。記錄平凡人的技術故事,分享有品質(盡量)的技術文章,偶爾也聊聊生活和理想。不販賣焦慮,不做標題黨。

如果本文對你有幫助,歡迎點贊關注監督,我們一起從青銅到王者


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM