在并發編程中使用生產者和消費者模式能夠解決絕大多數的并發問題。該模式通過平衡生產線程和消費線程的工作能力來提高程序整體處理數據的速度。
生產者和消費者模式:
在線程的世界中生產者就是產生數據的線程,而消費者則是消費數據的線程。在多線程開發中,如果生產者處理速度很快,而消費者處理速度很慢,那么生產者就必須等待消費者處理完才能繼續生產數據。同理,如果消費者處理速度很快,而生產者生產速度很慢,那么消費者就得等待生產者。為了解決這種生產消費能力不均衡的問題,便有了生產者和消費者模式。
在沒有使用生產者和消費者模式之前,往往生產者和消費者都是高耦合的。生產者每次生產一個數據后的邏輯處理都依賴于消費者的處理能力。而采用這種模式之后通過一個容器來解決了強耦合的問題,生產者與消費者之間不再進行通信,所以生產者生產后的數據無需依賴于消費者進行處理而是直接扔到阻塞隊列中,消費者同理。阻塞隊列相當于一個緩沖區,平衡了生產者和消費者的處理能力。
縱觀大多數的設計模式都會采用第三者來給雙方進行解耦,工廠模式的第三方是工廠類,模板模式的第三方是模板類。。。因此在一些實際業務場景中我們也可以通過添加第三者的方式來將整個業務進行解耦的處理操作。
實際應用場景:
郵件分類:
在一個場景中假設我們需要從一個郵箱中將該郵箱的所有郵件進行分類處理。對于最簡單的方法就是采用單線程不斷輪詢獲取到該郵箱的所有郵件并且進行循環處理郵件將其進行。這種方式則最簡單的方式但是這樣做的缺點顯而易見,如果郵箱中突然出現大量的郵件進行處理則會造成處理時間過長而造成性能下降。
那么我們想要提升處理吞吐量最簡單的則是采用多線程的模式來進行處理。那么采用什么樣方式處理最簡單呢。提到多線程處理我們肯定是要保證同步的,那么有沒有現成的線程安全的方案可以直接使用的呢,答案是阻塞隊列。采用生產者消費者的模式來使用阻塞隊列不僅能保證線程安全而且也提高了吞吐量更為重要的則是實現方式非常簡單。
接下來我們將說一說設計思路,就如上圖所示這是整個設計的架構圖。生產者則是無論種類不斷向阻塞隊列里投放郵件,而第一個消費者則是取出郵件后進行分類處理投放到不同的阻塞隊列中去,這個操作是cpu密集型操作而不是io密集型操作因此速度較快,而下面的幾個消費者則是專門進行處理這種類型的郵件的。采用上述的方式不僅提高了吞吐量更為重要的是解耦并且實現簡單。這就是生產者消費者模式。在實際的許多業務場景中使用這種方式進行處理則是很常見的。
線程池:
實際上我們最常使用的線程池它的內部本身就是采用的生產者消費者的模型機制。而這個模型設計的更加巧妙。
我們都知道線程池主要是由工作線程和任務隊列以及任務組成的。在最常見的生產者消費者模型中則是生產者將任務丟給隊列中,消費者從隊列中取出。但是對于線程池則是如果有空閑線程則可以將任務直接交給空閑線程去處理這樣做就省下了放到隊列中的步驟。
異步線程池:
線程池固然好用但是有兩個缺點:
1.線程池中的任務無法持久保存,如果主機宕機則會導致任務全部丟失
2.線程池只能處理本機的任務在集群中則無法去處理
因此對于以上缺點,異步線程池則能解決此類問題。結構如上圖所示。
可以看出,當有任務來到的時候生產者將任務放入到數據庫中,這次不再是阻塞隊列中去。原因很簡單:1.任務持久化處理,2.適用與分布式架構的模式。同時每個機器中開辟多個線程池,這多個線程池從數據庫中取出任務,同時我們賦予任務狀態來保證同步,狀態值:創建,執行中,重試,掛起,中止,完成。
創建:這個狀態則是生產者剛把任務放入數據庫中等待消費者處理
執行中:消費者拿到任務后修改狀態值表示這個任務以及被消費者處理中,其他消費者則在此過程中無法處理該任務
重試:消費者處理過程中發生異常情況則將此狀態為設置為重試,根據不同任務的類型對應不同重試的策略
掛起:當前任務等待某個前置任務的完成之后才能執行本任務那么就需要將其設置為掛起,業務員自己設置掛起后的策略
中止:由于某種原因本任務則無需執行則會將其設置為中止狀態
完成:完成了本任務的執行
那么異步線程池需要注意哪些地方呢
任務隔離:異步任務往往是有多種類型的,但是系統的資源是有限的。如果采用優先級的方式那么很有可能會造成某些高優先級的任務永遠無法執行,因此我們采用的策略則是根據不同的任務我們采用不同的線程池進行處理,也就是任務類型分組處理,我們可以通過控制分組線程池的多少來進行進行控制處理的速率以及資源的部署。這樣就不會造成某些任務永遠無法處理的情況頂多是那些分配的資源少的任務類型處理速度上較慢。
重試策略:根據不同的任務類型設置不同的重試策略,有些任務可能要求實時性高。那么每次的重試間隔就會非常短,如果對實時性要求不高,可以采用默認的重試策略來對其進行重試。每個類型可以設置不同的重試次數
勿本機存儲:對于不同機器上的線程池不要采用本機存儲的方式因為整個項目采用的是集群部署,如果采用本機存儲,某些后續任務可能有著上一個任務存儲資源的存儲路徑,如果前置任務在機器A中完成那么后置任務將會無法找到這個資源
異步屬性:對于所有任務都必須要帶有任務的狀態,名稱,下次執行時間,執行次數,任務類型,報錯類型等等任務屬性。這樣對于后續任務的進展將會有很大的幫助簡化了不少的業務復雜度