在IT技術面試過程中,我們經常會遇到生産者消費者問題(Producer-consumer problem), 這是多線程并發協作問題的經典案例。場景中包含三個對象,生産者(Producer),消費者(Consumer)以及一個固定大小的緩沖區(Buffer)。生産者的主要作用是不斷生成資料放到緩沖區,消費者則從緩沖區不斷消耗資料。該問題的關鍵是如何線程安全的操作共享資料塊,保證生産者線程和消費者線程可以正确的更新資料塊,主要考慮 1. 生産者不會在緩沖區滿時加入資料. 2. 消費者應當停止在緩沖區時消耗資料. 3. 在同一時間應當隻允許一個生産者或者消費者通路共享緩沖區(這一點是對于互斥操作通路共享區塊的要求)。
解決問題以上問題通常有信号量,wait & notify, 管道或者阻塞隊列等幾種思路。本文以Java語言為例一一進行舉例講解。
信号量(Semaphore)也稱信号燈,是用來控制資源被同時通路的個數,比如控制通路資料庫最大連接配接數的數量,線程通過acquire()獲得連接配接許可,完成資料操作後,通過release()釋放許可。對于生産者消費者問題來說,為了滿足線程安全操作的要求,同一時間我們隻允許一個線程通路共享資料區,是以需要一個大小為1的信号量mutex來控制互斥操作。注意到我們還定義了notFull 和 notEmpty 信号量,notFull用于辨別目前可用區塊的空間大小,當notFull size 大于0時表明"not full", producer 可以繼續生産,等于0時表示空間已滿,無法繼續生産;同樣,對于notEmpty信号量來說,大于0時表明 "not empty", consumer可以繼續消耗,等于0 時表明沒有産品,無法繼續消耗。notFull初始size 為5 (5個available空間可供生産),notEmpty初始為0(沒有産品可供消耗)。
Java Object對象類中包含三個final methods來允許線程之間進行通信,告知資源的狀态。它們分别是wait(), notify(), 和notifyAll()。
wait(): 顧名思義告訴目前線程釋放鎖,陷入休眠狀态(waiting狀态),等待資源。wait 方法本身是一個native method,它在Java中的使用文法如下所示:
notify(): 用于喚醒waiting狀态的線程, 同時釋放鎖,被喚醒的線程可以重新獲得鎖通路資源。它的基本文法 如下
notifyAll(): 不同于notify(),它用于喚醒所有處于waiting狀态的線程。文法如下:
說完了這三個方法,來看下如何使用wait & notify(All) 來解決我們的問題。新的DataWareHouse 類如下所示:
Note: 在方法上使用synchronized 等價于在方法體内使用synchronized(this),兩者都是使用this對象作為鎖。
生産者和消費者類,以及測試代碼和 信号量 section 相同,不做重複列舉了。
管道Pipe是實作程序或者線程(線程之間通常通過共享記憶體實作通訊,而程序則通過scoket,管道,消息隊列等技術)之間通信常用方式,它連接配接輸入流和輸出流,基于生産者- 消費者模式建構的一種技術。具體實作可以通過建立一個管道輸入流對象和管道輸出流對象,然後将輸入流和輸出流就行連結,生産者通過往管道中寫入資料,而消費者在管道資料流中讀取資料,通過這種方式就實作了線程之間的互相通訊。
具體實作代碼如下所示
阻塞隊列(BlockingQueue),具有1. 當隊列滿了的時候阻塞入隊列操作 2. 當隊列空了的時候阻塞出隊列操作 3. 線程安全 的特性,因而阻塞隊列通常被視為實作生産消費者模式最便捷的工具,其中DataWareHouse類實作代碼如下:
生産者和消費者類,以及測試代碼和 信号量 section 相同,在此不做重複列舉了。
生産者消費者問題是面試中經常會遇到的題目,本文總結了幾種常見的實作方式,面試過程中通常不必要向面試官描述過多實作細節,說出每種實作方式的特點即可。希望能給大家帶來幫助。
https://howtodoinjava.com/java/multi-threading/wait-notify-and-notifyall-methods/
歡迎轉載