並行程序設計模式

並行程序設計模式一般有Future模式、Master-Slave模式、保護暫停模式、不變模式、生產者/消費者模式等。

1. Future模式

Future模式有點類似商品訂單。比如在進行網上購物時,當看中某一件商品時,就可以提交訂單。當訂單處理完畢後,便可在家裏等待商品送貨上門。賣家根據訂單從倉庫裏取貨,並配送到客戶手上。在大部分情況下,商家對訂單的處理並不那麼快,有時甚至需要幾天時間。而在這段時間內,客戶不需要在家裏等待,而可以去處理其他事務。

將此例類推到程序設計中,當某一段程序提交了一個請求,期望得到一個答覆。但非常不幸的是,服務程序對這個請求的處理可能很慢,比如,這個請求可能是通過互聯網、HTTP或者Web Service等並不太高效的方式調用的。在傳統的單線程環境下,調用函數是同步的,也就是說它必須等到服務程序返回結果後,才能進行其他處理。而在Future模式下,調用方式改爲異步,而原先等待返回的時間段,在主調用函數中,則可用於處理其他事務。雖然call本身仍然需要很長一段時間來處理程序,但是,服務程序不等數據處理完成便立即返回客戶端一個僞造的數據(相當於商品的訂單,而不是商品本身),實現了Future模式的客戶端在拿到這個返回結果後,並不急於對其進行處理,而去調用了其他業務邏輯,充分利用了等待時間,這就是Future模式的核心所在。在完成了其他業務邏輯的處理後,最後再使用返回比較慢的Future數據。這樣,在整個調用過程中,就不存在無謂的等待,充分利用了所有的時間片段,從而提高系統的響應速度。

Future模式的主要參與者包括:

n Main—系統啓動,調用Client發出請求;

n Client—返回Data對象,立即返回FutureData,並開啓ClientThread線程裝配RealData;

n Data—返回數據的接口;

n FutureData—Future數據,構造很快,但是是一個虛擬的數據,需要裝配RealData;

n RealData—真實數據,其構造是比較慢的。

代碼清單4-18 Future模式示例

/*

 * Main函數主要負責調用Client發起請求,並使用返回的數據

 */

public class FutureMain{

      public static void main(String[] args){

          Client client = new Client();

          //這裏會立即返回,因爲得到的是FutureData而不是RealData

          Data data =client.request("name");

          System.out.println("虛擬請求結束");

          try{

              //這裏可以用一個Sleep代替對其他業務邏輯的處理,在實際場景中,RealData被創建,節約了等待時間

              Thread.sleep(1000);

          }catch(InterruptedException ex){

              ex.printStackTrace();

          }

          //使用真實數據

          System.out.println(data.getResult);

      }

}

2. Master-Slave模式

Master-Slave(主從)模式主導的系統架構一般由兩類線程實現,Master線程負責接收和分發任務(將任務拆成一個個子任務),Worker線程負責處理子任務,每個Worker線程只處理部分任務,所有Worker線程共同完成所有任務的處理。

該模式的好處在於能夠將一個大的任務拆分成若干個小的任務,從而交給不同的Worker並行的進行處理,進而提高系統的吞吐量。另外,Client端一旦提交任務後,Master線程完成任務的接收和分發後立即返回,因此對客戶端來說,整個過程也是異步進行的。

一般的實現思路如下:

(1)Master中首先需要維護一個隊列Queue,用於接收任務,同時維護一個所有Worker線程的threadMap,以及每個子任務對應的處理結果集resultMap,這裏由於涉及多線程同時訪問resultMap,因此一般使用JDK中的ConcurrentHashMap實現;

(2)Worker線程實現Runnable或繼承Thread,通過Master中的Queue獲取拆分後的子任務,並進行業務處理,並將處理結果設置到resultMap中以便Master獲取到;

(3)Main入口函數則負責客戶端請求的提交(需要先進程拆解),以及通過Master獲取各個Worker的結果後進行合併,最後返回給客戶端完成處理過程。

目前主流的MapReduce框架、集羣框架很多都是採用該種模式架構實現的。

3. 保護暫停(Guarded Suspension)模式   

所謂“保護暫停”模式,核心思想在於僅當服務進程準備好時,才提供服務。它的好處在於既能保證所有的客戶端請求均不丟失,同時也避免了服務器由於同時處理太多的請求而崩潰的現象,有效降低系統的瞬時負載,有助於系統穩定性。

其實這種通過中間加一層Queue做緩衝的模式在工作中用的很多,類似“ClientThread -> Request Queue -> ServerThread”的情況比比皆是,只不過可能實際中我們往往會結合其他方法一起使用,例如:

(1)將ClientThread和ServerThread均爲多個,則變爲經典的“生產者-消費者”模式;

(2)如果將ServerThread拆爲1個Master和多個Worker,則又是上面提到的“Master-Worker”模式;

(3)如果處理的請求需要返回結果,那麼又需要和FutureTask結合起來使用(即客戶端的請求中需要帶上FutureData,並在ServerThread中爲FutureData設置上RealData)。

4. 不變模式

併發多線程程序中,當多線程對同一個對象進行讀寫操作時,爲了確保對象數據的一致性和準確性,必須進行同步操作,而這正是對系統性能損失嚴重的地方。因此,爲了提高併發程序的性能,我們可以創建一種不可改變的對象,使用過程中保持不變性。這就是所謂“不變”模式。Java中這種模式用的很廣,如String、Boolean、Short、Integer、Long、Byte等。它的好處在於通過迴避問題而不是解決問題的態度來處理多線程併發訪問控制,但缺點是隻適用於對象創建後內部狀態和數據不可發生變化的情況。

Java中不變模式的實現很簡單,按照OO的思想[1],只需要滿足以下幾點即可:

(1)將對象的所有屬性設爲privatefinal的;

(2)通過final修飾class確保類不可被繼承;

(3)去掉對象中的所有settXX方法;

(4)有包含所有屬性的構造函數用於創建對象。

5. 生產者-消費者模式

生產者線程向內存緩衝區提交任務,消費者線程從內存緩衝區獲取任務並進行處理。它的好處在於將生產者線程和消費者線程進行解耦,優化系統整體結構,緩解性能瓶頸對系統性能的影響。

Java中,一般來說使用LinkedBlockingQueue作爲上面說的“內存緩衝區”,它是阻塞型BlockingQueue的一種使用Link List的實現,它對頭和尾採用兩把不同的鎖,與ArrayBlockingQueue相比提高了吞吐量,適合於實現“生產者-消費者”模式。實現的大致思路如下:

(1)創建Producer類,實現run方法用於提交任務;

(2)創建Consumer類,實現run方法用於處理任務;

(3)Main函數中建立緩衝區,若干個生產者,若干個消費者,創建線程池並開始使這些線程工作起來。



 感興趣的朋友可關注公衆號—麥克叔叔每晚十點說,一起交流與學習。

發佈了428 篇原創文章 · 獲贊 197 · 訪問量 82萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章