java線程中生產者與消費者的問題

来源:http://www.cnblogs.com/czl362326/archive/2016/07/14/5665009.html
-Advertisement-
Play Games

一、概念 生產者與消費者問題是一個金典的多線程協作的問題.生產者負責生產產品,並將產品存放到倉庫;消費者從倉庫中獲取產品並消費。當倉庫滿時,生產者必須停止生產,直到倉庫有位置存放產品;當倉庫空時,消費者必須停止消費,直到倉庫中有產品。 解決生產者/消費者問題主要用到如下幾個技術:1.用線程模擬生產者 ...


一、概念

  生產者與消費者問題是一個金典的多線程協作的問題.生產者負責生產產品,並將產品存放到倉庫;消費者從倉庫中獲取產品並消費。當倉庫滿時,生產者必須停止生產,直到倉庫有位置存放產品;當倉庫空時,消費者必須停止消費,直到倉庫中有產品。

  解決生產者/消費者問題主要用到如下幾個技術:1.用線程模擬生產者,在run方法中不斷地往倉庫中存放產品。2.用線程模擬消費者,在run方法中不斷地從倉庫中獲取產品。3

.倉庫類保存產品,當產品數量為0時,調用wait方法,使得當前消費者線程進入等待狀態,當有新產品存入時,調用notify方法,喚醒等待的消費者線程。當倉庫滿時,調用wait方法,使得當前生產者線程進入等待狀態,當有消費者獲取產品時,調用notify方法,喚醒等待的生產者線程。

二、實例

  

package book.thread.product;

public class Consumer extends Thread{
  private Warehouse warehouse;//消費者獲取產品的倉庫
  private boolean running = false;//是否需要結束線程的標誌位
  public Consumer(Warehouse warehouse,String name){
    super(name);
    this.warehouse = warehouse;
  }
  public void start(){
    this.running = true;
    super.start();
  }
  public void run(){
    Product product;
    try {
      while(running){
        //從倉庫中獲取產品
        product = warehouse.getProduct();
        sleep(500);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  //停止消費者線程
  public void stopConsumer(){
    synchronized(warehouse){
      this.running = false;
      warehouse.notifyAll();//通知等待倉庫的線程
    }
  }
  //消費者線程是否在運行
  public boolean isRunning(){
    return running;
  }
}

 

package book.thread.product;

public class Producer extends Thread{
    private Warehouse warehouse;//生產者存儲產品的倉庫
  private static int produceName = 0;//產品的名字
  private boolean running = false;//是否需要結束線程的標誌位

  public Producer(Warehouse warehouse,String name){
    super(name);
    this.warehouse = warehouse;
  }
  public void start(){
    this.running = true;
    super.start();
  }
  public void run(){
    Product product;
    //生產並存儲產品
    try {
    while(running){
      product = new Product((++produceName)+"");
      this.warehouse.storageProduct(product);
      sleep(300);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  //停止生產者線程
  public void stopProducer(){
    synchronized(warehouse){
      this.running = false;
      //通知等待倉庫的線程
      warehouse.notifyAll();
    }
  }
  //生產者線程是否在運行
  public boolean isRunning(){
    return running;
  }
}

 

package book.thread.product;

public class Product {
  private String name;//產品名
  public Product(String name){
    this.name = name;
  }
  public String toString(){
    return "Product-"+name;
  }
}

 

package book.thread.product;

//產品的倉庫類,內部採用數組來表示迴圈隊列,以存放產品
public class Warehouse {
  private static int CAPACITY = 11;//倉庫的容量
  private Product[] products;//倉庫里的產品
  //[front,rear]區間的產品未被消費
  private int front = 0;//當前倉庫中第一個未被消費的產品的下標
  private int rear = 0;//倉庫中最後一個未被消費的產品下標加1
  public Warehouse(){
    this.products = new Product[CAPACITY];
  }
  public Warehouse(int capacity){
    this();
    if(capacity > 0){
      CAPACITY = capacity +1;
      this.products = new Product[CAPACITY];
    }
  }

  //從倉庫獲取一個產品
  public Product getProduct() throws InterruptedException{
    synchronized(this){
      boolean consumerRunning = true;//標誌消費者線程是否還在運行
      Thread currentThread = Thread.currentThread();//獲取當前線程
      if(currentThread instanceof Consumer){
        consumerRunning = ((Consumer)currentThread).isRunning();
      }else{
        return null;//非消費者不能獲取產品
      }
      //若消費者線程在運行中,但倉庫中沒有產品了,則消費者線程繼續等待
      while((front==rear) && consumerRunning){
        wait();
        consumerRunning = ((Consumer)currentThread).isRunning();
      }
      //如果消費者線程已經停止運行,則退出該方法,取消獲取產品
      if(!consumerRunning){
        return null;
      }
      //獲取當前未被消費的第一個產品
      Product product = products[front];
      System.out.println("Consumer[" + currentThread.getName()+"] getProduct:"+product);
      //將當前未被消費產品的下標後移一位,如果到了數組末尾,則移動到首部
      front = (front+1+CAPACITY)%CAPACITY;
      System.out.println("倉庫中還沒有被消費的產品數量:"+(rear+CAPACITY-front)%CAPACITY);
      //通知其他等待線程
      notify();
      return product;
    }
  }
  //向倉庫存儲一個產品
  public void storageProduct(Product product) throws InterruptedException{
  synchronized(this){
    boolean producerRunning = true;//標誌生產者線程是否在運行
    Thread currentThread = Thread.currentThread();
    if(currentThread instanceof Producer){
      producerRunning = ((Producer)currentThread).isRunning();
    }else{
      return;
    }
    //如果最後一個未被消費的產品與第一個未被消費的產品的下標緊挨著,則說明沒有存儲空間了。
    //如果沒有存儲空間了,而生產者線程還在運行,則生產者線程等待倉庫釋放產品
    while(((rear+1)%CAPACITY == front) && producerRunning){
      wait();
      producerRunning = ((Producer)currentThread).isRunning();
    }
    //如果生產線程已經停止運行了,則停止產品的存儲
    if(!producerRunning){
      return;
    }
    //保存產品到倉庫
    products[rear] = product;
    System.out.println("Producer[" + Thread.currentThread().getName()+"] storageProduct:" + product);
    //將rear下標迴圈後移一位
    rear = (rear + 1)%CAPACITY;
    System.out.println("倉庫中還沒有被消費的產品數量:"+(rear + CAPACITY -front)%CAPACITY);
    notify();
    }
  }
}

 

package book.thread.product;

public class TestProduct {
  public static void main(String[] args) {
    Warehouse warehouse = new Warehouse(10);//建立一個倉庫,容量為10
    //建立生產者線程和消費者
    Producer producers1 = new Producer(warehouse,"producer-1");
    Producer producers2 = new Producer(warehouse,"producer-2");
    Producer producers3 = new Producer(warehouse,"producer-3");
    Consumer consumer1 = new Consumer(warehouse,"consumer-1");
    Consumer consumer2 = new Consumer(warehouse,"consumer-2");
    Consumer consumer3 = new Consumer(warehouse,"consumer-3");
    Consumer consumer4 = new Consumer(warehouse,"consumer-4");
    //啟動生產者線程和消費者線程
    producers1.start();
    producers2.start();
    consumer1.start();
    producers3.start();
    consumer2.start();
    consumer3.start();
    consumer4.start();
    //讓生產者/消費者程式運行1600ms
    try {
      Thread.sleep(1600);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //停止消費者線程
    producers1.stopProducer();
    consumer1.stopConsumer();
    producers2.stopProducer();
    consumer2.stopConsumer();
    producers3.stopProducer();
    consumer3.stopConsumer();
    consumer4.stopConsumer();
  }
}

輸出結果:

Producer[producer-1] storageProduct:Product-1
倉庫中還沒有被消費的產品數量:1
Consumer[consumer-2] getProduct:Product-1
倉庫中還沒有被消費的產品數量:0
Producer[producer-3] storageProduct:Product-3
倉庫中還沒有被消費的產品數量:1
Producer[producer-2] storageProduct:Product-2
倉庫中還沒有被消費的產品數量:2
Consumer[consumer-3] getProduct:Product-3
倉庫中還沒有被消費的產品數量:1
Consumer[consumer-1] getProduct:Product-2
倉庫中還沒有被消費的產品數量:0
Producer[producer-1] storageProduct:Product-4
倉庫中還沒有被消費的產品數量:1
Consumer[consumer-4] getProduct:Product-4
倉庫中還沒有被消費的產品數量:0
Producer[producer-3] storageProduct:Product-6
倉庫中還沒有被消費的產品數量:1
Producer[producer-2] storageProduct:Product-5
倉庫中還沒有被消費的產品數量:2
Consumer[consumer-1] getProduct:Product-6
倉庫中還沒有被消費的產品數量:1
Consumer[consumer-2] getProduct:Product-5
倉庫中還沒有被消費的產品數量:0
Producer[producer-1] storageProduct:Product-7
倉庫中還沒有被消費的產品數量:1
Consumer[consumer-3] getProduct:Product-7
倉庫中還沒有被消費的產品數量:0
Producer[producer-3] storageProduct:Product-8
倉庫中還沒有被消費的產品數量:1
Producer[producer-2] storageProduct:Product-9
倉庫中還沒有被消費的產品數量:2
Consumer[consumer-4] getProduct:Product-8
倉庫中還沒有被消費的產品數量:1
Producer[producer-1] storageProduct:Product-10
倉庫中還沒有被消費的產品數量:2
Producer[producer-3] storageProduct:Product-11
倉庫中還沒有被消費的產品數量:3
Producer[producer-2] storageProduct:Product-12
倉庫中還沒有被消費的產品數量:4
Consumer[consumer-1] getProduct:Product-9
倉庫中還沒有被消費的產品數量:3
Consumer[consumer-2] getProduct:Product-10
倉庫中還沒有被消費的產品數量:2
Consumer[consumer-3] getProduct:Product-11
倉庫中還沒有被消費的產品數量:1
Producer[producer-3] storageProduct:Product-13
倉庫中還沒有被消費的產品數量:2
Producer[producer-1] storageProduct:Product-14
倉庫中還沒有被消費的產品數量:3
Producer[producer-2] storageProduct:Product-15
倉庫中還沒有被消費的產品數量:4
Consumer[consumer-4] getProduct:Product-12
倉庫中還沒有被消費的產品數量:3
Consumer[consumer-1] getProduct:Product-13
倉庫中還沒有被消費的產品數量:2
Consumer[consumer-2] getProduct:Product-14
倉庫中還沒有被消費的產品數量:1
Producer[producer-1] storageProduct:Product-16
倉庫中還沒有被消費的產品數量:2
Producer[producer-3] storageProduct:Product-17
倉庫中還沒有被消費的產品數量:3
Producer[producer-2] storageProduct:Product-18
倉庫中還沒有被消費的產品數量:4

分析:在main方法中建立了一個產品倉庫,並未該倉庫關聯了3個生產者線程和4個消費者線程,啟動這些線程,使生產 者/消費者模型運作起來,當程式運行1600ms時,所有的生產者停止生產產品,消費者停止消費產品。

  生產者線程Product在run方法中沒300ms便生產一個產品,並存入倉庫;消費者線程Consumer在run方法中沒500ms便從倉庫中取一個產品。

  倉庫類Warehouse負責存放產品和發放產品。storageProduct方法負責存儲產品,當倉庫滿時,當前線程進入等待狀態,即如果生產者線程A在調用storageProduct方法以存儲產品時,發現倉庫已滿,無法存儲時,便會進入等待狀態。當存儲產品成功時,調用notify方法,喚醒等待的消費者線程。

  getProduct方法負責提前產品,當倉庫空時,當前線程進入等待狀態,即如果消費者線程B在調用getProduct方法以獲取產品時,發現倉庫空了,便會進入等待狀態。當提取產品成功時,調用notify方法,喚醒等待的生產者線程。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ...
  • Vs2015、Vs2013親測可行 感謝NopCommerce探索群(75272942)-深圳-阿海(13913127)提供解決方案 ...
  • 一.概述 java迴圈語句分為四種形式,分別是 while, do/while, for, foreach; python中迴圈語句有兩種,while,for; JavaScript中迴圈語句有四種,while,do/while,for,for/in jQuery迴圈語句each 二、java迴圈語 ...
  • 前言:本篇主要針對資料庫的操作,在這裡不適用hibernate或者mybatis,用最原始的JDBC進行講解,通過瞭解這些原理以後更容易理解和學習hibernate或mybatis。 1:jdbc的簡單操作 首先需要下載jdbc驅動的jar包 如果不想下載在C:\Program Files (x86 ...
  • 對於這個月,我主要是在學堂線上上學習MOOC。鄧老師講的很好 !!!這個月,我主要學習數據結構,還不時的到學校OJ上出出題。(PS:我也是學校OJ上的管理員)今天學校停電了。。。OJ管理系統都關了。。。我只好在家開著電腦邊聽歌邊刷清華北大OJ上的簡單題。。。因為難題實在不會。。。暴力我估計都會超時。 ...
  • 總是以為自己瞭解了測試驅動開發,其實做起來和瞭解根本不是一回事。原來覺得代碼清晰得很,後來試驗了一下才知道那是自己的錯覺。這次,讓我們拋卻Eclipse的自動補全功能,來一場真正的測試驅動開發吧。 項目描述:這是一個很簡單的項目,目標是掃描磁碟上所有特定格式的文件,將其路徑存儲下來,通過程式可以快捷 ...
  • ...
  • 槽和普通成員函數一樣,可以是虛函數、被重載,可以是公有、私有、保護的。它可以被其它C++成員函數調用。 槽連接了信號,當發射這個信號時,槽會被自動調用。 連接函數: bool QObject::connect ( const QObject * sender, const char * signal ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...