(一道面試題)線程深入-生產者消費者問題

来源:http://www.cnblogs.com/hckblogs/archive/2017/11/19/7858545.html
-Advertisement-
Play Games

一.生產者消費者問題? 1.生產者消費者問題(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共用固定大小緩衝區的線程——即所謂的“生產者”和“消費者”——在實際運行時 ...


一.生產者消費者問題?

1.生產者消費者問題(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共用固定大小緩衝區線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是生成一定量的數據放到緩衝區中,然後重覆此過程。與此同時,消費者也在緩衝區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入數據,消費者也不會在緩衝區中空時消耗數據。

2.解決辦法:

要解決該問題,就必須讓生產者在緩衝區滿時休眠(要麼乾脆就放棄數據),等到下次消費者消耗緩衝區中的數據的時候,生產者才能被喚醒,開始往緩衝區添加數據。同樣,也可以讓消費者在緩衝區空時進入休眠,等到生產者往緩衝區添加數據之後,再喚醒消費者。通常採用進程間通信的方法解決該問題,常用的方法有信號燈法等。如果解決方法不夠完善,則容易出現死鎖的情況。出現死鎖時,兩個線程都會陷入休眠,等待對方喚醒自己。該問題也能被推廣到多個生產者和消費者的情形。

二.編碼實現生產者消費者問題

1.先上結果圖:我定義了三個生產者分別時張飛,趙雲還有關羽,兩個消費者分別時曹操還有劉備.

先上結果圖是為了說明:多線程說白了就是對cpu資源的搶奪,誰搶到了就可以執行自己的方法.

 

2.生產者消費者實現步驟:

(1).因為生產者跟消費者共用一塊區域,這裡我將這塊區域定義為“倉庫”。

(2).倉庫是有容量上限的,當數量達到上限後,生產者不允許繼續生產產品.當前線程進入等待狀態,等待其他線程喚醒。

(3).當倉庫沒有產品時,消費者不允許繼續消費,當前線程進入等待狀態,等待其他線程喚醒。

3.代碼的實現:

(1).生產者跟消費者之間消費的是產品,所有先定義一個產品類:

public class Product {
	//定義產品的唯一ID
    int id;
    //定義構造方法初始化產品id
	public Product(int id) {
		this.id=id;
		// TODO Auto-generated constructor stub
	}
}

(2).定義一個倉庫用來存放產品.

public class Repertory {
	//定義一個集合類用於存放產品.規定倉庫的最大容量為10.
    public LinkedList<Product> store=new LinkedList<Product>();
	public LinkedList<Product> getStore() {
		return store;
	}
	public void setStore(LinkedList<Product> store) {
		this.store = store;
	}
	/* 生產者方法
	 * push()方法用於存放產品.
	 * 參數含義:第一個是產品對象
	 * 第二個是線程名稱,用來顯示是誰生產的產品.
	 * 使用synchronized關鍵字修飾方法的目的:
	 * 最多只能有一個線程同時訪問該方法.
	 * 主要是為了防止多個線程訪問該方法的時候,將參數數據進行的覆蓋,從而發生出錯.
	 */
	public synchronized void push(Product p,String threadName)
	{
		/* 倉庫容量最大值為10,當容量等於10的時候進入等待狀態.等待其他線程喚醒
		 * 喚醒後繼續迴圈,等到倉庫的存量小於10時,跳出迴圈繼續向下執行準備生產產品.
		 */	
		while(store.size()==10){
			try {
				//列印日誌
				System.out.println(threadName+"報告:倉庫已滿--->進入等待狀態--->呼叫老大過來消費");
				//因為倉庫容量已滿,無法繼續生產,進入等待狀態,等待其他線程喚醒.
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//喚醒所有等待線程
		this.notifyAll();
		//將產品添加到倉庫中.
		store.addLast(p);
		//列印生產日誌
		System.out.println(threadName+"生產了:"+p.id+"號產品"+" "+"當前庫存來:"+store.size());
		try {
			//為了方便觀察結果,每次生產完後等待0.1秒.
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
	/* 消費者方法
	 * pop()方法用於存放產品.
	 * 參數含義:線程名稱,用來顯示是誰生產的產品.
	 * 使用synchronized關鍵字修飾方法的目的:
	 * 最多只能有一個線程同時訪問該方法.
	 * 主要是為了防止多個線程訪問該方法的時候,將參數數據進行的覆蓋,從而發生出錯.
	 */
	public synchronized void pop(String threadName){
		/* 當倉庫沒有存貨時,消費者需要進行等待.等待其他線程來喚醒
		 * 喚醒後繼續迴圈,等到倉庫的存量大於0時,跳出迴圈繼續向下執行準備消費產品.
		 */
		while(store.size()==0)
		{
			try {
				//列印日誌
				System.out.println(threadName+"下命令:倉庫已空--->進入等待狀態--->命令小弟趕快生產");
				//因為倉庫容量已空,無法繼續消費,進入等待狀態,等待其他線程喚醒.
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//喚醒所有等待線程
		this.notifyAll();
		//store.removeFirst()方法將產品從倉庫中移出.
		//列印日誌
		System.out.println(threadName+"消費了:"+store.removeFirst().id+"號產品"+" "+"當前庫存來:"+store.size());
		try {
			//為了方便觀察結果,每次生產完後等待1秒.
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

(3).定義生產者

public class Producer implements Runnable {
	//定義一個靜態變數來記錄產品號數.確保每一個產品的唯一性.
	public static  Integer count=0;
	//定義倉庫
    Repertory repertory=null;
    //構造方法初始化repertory(倉庫)
	public Producer(Repertory repertory) {
		this.repertory=repertory;
	}
	/* run()方法因為該方法中存在非原子性操作count++;
	 * 當多個線程同時訪問時會發生count++的多次操作,導致出錯
	 * 為該方法添加同步錯做,確保每一次只能有一個生產者進入該模塊。
	 * 這樣就能保證count++這個操作的安全性.
	 */
	@Override
	public void run() {
			while (true) {		
				synchronized(Producer.class){
					count++;
					Product product=new Product(count);
					repertory.push(product,Thread.currentThread().getName());
				}
			    			
			
		}
		
	}
}

(4).定義一個消費者

public class Consumer implements Runnable {
	//定義倉庫
	Repertory repertory=null;
	//構造方法初始化repertory(倉庫)
	public Consumer(Repertory repertory) {
		this.repertory=repertory;
	}
	//實現run()方法,並將當前的線程名稱傳入.
	@Override
	public  void run() {
		while(true){
			repertory.pop(Thread.currentThread().getName());
		}
	}

}

(5).測試類

public class TestDemo {
  public static void main(String[] args) {
	//定義一個倉庫,消費者和生產者都使用這一個倉庫
	Repertory repertory=new Repertory();
	//定義三個生產者(p1,p2,p3)
	Producer p1=new Producer(repertory);
	Producer p2=new Producer(repertory);
	Producer p3=new Producer(repertory);
	//定義兩個消費者(c1,c2)
	Consumer c1=new Consumer(repertory);
	Consumer c2=new Consumer(repertory);
	//定義5個線程(t1,t2,t3,t4,t5)
	Thread t1=new Thread(p1,"張飛");
	Thread t2=new Thread(p2,"趙雲");
	Thread t3=new Thread(p3,"關羽");
	Thread t4=new Thread(c1,"劉備");
	Thread t5=new Thread(c2,"曹操");
	//因為關羽跟趙雲的生產積極性高,所以把他們的線程優先順序調高一點
	t2.setPriority(10);
	t3.setPriority(10);
	//啟動線程
	t1.start();
	t2.start();
	t3.start();
	t4.start();
	t5.start();
  }
}

三.總結:

生產者消費者問題我也是弄了好幾天才略有頭緒,弄完了就把自己的認識貼了出來,歡迎各位指點

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 初稿:2017-11-19 13:05:57 4種鏈表 鏈表和數組的區別 數組初始容量一旦確定,不能再改變,適合要處理的數據量已知的情況。 未知要處理的數據量使用數組,可能造成空間浪費或容量不足,雖然有動態數組可擴容,但是頻繁擴容會使系統產生很大的開銷。 鏈表容量不限,長度與元素個數相同,但是需要額 ...
  • hasattr(object, name)判斷一個對象裡面是否有name屬性或者name方法,返回BOOL值,有name特性返回True, 否則返回False。需要註意的是name要用括弧括起來 getattr(object, name[,default])獲取對象object的屬性或者方法,如果存 ...
  • 面向對象概念 面向對象是利用類和對象來創建各種模型對真實世界進行描述,也能使程式變得簡單。 class 類 一個類即對一類擁有相同屬性的對象的抽象。其中類定義了這些對象都具備非屬性以及方法。 object 對象 一個對象即是一個類的實例化後的實例,一個類必須經過實例化後才能在程式中調用,一個類可以有 ...
  • 1. 框架代碼 用 PyCharm 新建一個名為 SimplePaintApp 的項目,然後新建一個名為 simple_paint_app.py 的 Python 源文件, 在代碼編輯器中,輸入以下框架代碼 運行上面的代碼,將顯示一個黑色背景的視窗 看起來很沒勁的樣子,不過你可不要小瞧這幾行代碼。這 ...
  • 在方法裡面建立properties對象 Properties pps = new Properties(); 調用.load()方法 pps.load(new FileInputStream("E:\\workplace\\testStudent\\src\\we.properties")); 整個 ...
  • 大家在中學就已經學過變數的概念了。例如:我們令 x = 100,則可以推出 x*2 = 200 試試下麵這段 Python 代碼 運行上面的代碼,小海龜將畫出下麵的圖案 x = 100 聲明瞭變數 x,並將它賦值為 100,用大家熟悉的中學數學語言來說,就是“令 x 等於 100” 接下來的代碼中 ...
  • 前面我們學習了numpy,現在我們來學習一下pandas。 Python Data Analysis Library 或 pandas 主要用於處理類似excel一樣的數據格式,其中有表頭、數據序列號以及實際的數據,而numpy就僅僅包含了實際的數據。 安裝 直接輸入: 最基本用法 輸出: 我們可以 ...
  • List<Map<String, Object>> queryData = configDao.queryConfig( expressions, conditions, expressions); Collections.sort(queryData, new Comparator<Map<Str ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...