這里總結幾種常用的並行程序設計方法,其中部分文字源自《Java程序性能優化》一書中,還有部分文字屬於個人總結,如有不對,請大家指出討論。
Future模式
一句話,將客戶端請求的處理過程從同步改為異步,以便將客戶端解放出來,在服務端程序處理期間可以去干點其他事情,最后再來取請求的結果。
好處在於整個調用過程中不需要等待,可以充分利用所有的時間片段,提高系統的響應速度。
JDK中已經內置實現了FutureTask,使用起來非常方便,同時還可以取消Future任務,或者設置Future任務的超時時間。
1)實現一個Callable接口,實現服務端的具體的業務邏輯計算:
public class RealData implements Callable<String> { public String call() throws Exception {...} }
2)定義FutureTask,提交執行,獲取返回結果:
// 1. create future task FutureTask<String> future = new FutureTask<String>(new RealData("aaa")); // 2. submit future task ExecutorService exexutor = Executors.newFixedThreadPool(1); exector.submit(future); // 3. do other thing ...... // 4. get result, waiting util call method finished System.out.println("Result:" + future.get());
Master-Worker模式
由兩類線程實現: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的結果后進行合並,最后返回給客戶端完成處理過程。
Guarded Suspension模式
所謂“保護暫停”模式,核心思想在於僅當服務進程准備好時,才提供服務。
好處在於既能保證所有的客戶端請求均不丟失,同時也避免了服務器由於同時處理太多的請求而崩潰的現象,有效降低系統的瞬時負載,有助於系統穩定性。
其實這種通過中間加一層Queue做緩沖的模式在工作中用的很多,類似“ClientThread -> Request Queue -> ServerThread”的情況比比皆是,只不過可能實際中我們往往會結合其他方法一起使用,例如:
1)將ClientThread和ServerThread均為多個,則變為經典的“生產者-消費者”模式;
2)如果將ServerThread拆為1個Master和多個Worker,則又是上面提到的“Master-Worker”模式;
3)如果處理的請求需要返回結果,那么又需要和FutureTask結合起來使用(即:客戶端的請求中需要帶上FutureData,並在ServerThread中為FutureData設置上RealData)。
不變模式
並發多線程程序中,當多線程對同一個對象進行讀寫操作時,為了確保對象數據的一致性和准確性,必須進行同步操作,而這正是對系統性能損失嚴重的地方。因此,為了提高並發並發程序的性能,我們可以創建一種不可改變的對象,使用過程中保持不變性。這就是所謂“不變”模式。Java中這種模式用的很廣,如String、Boolean、Short、Integer、Long、Byte等。
好處在於通過回避問題而不是解決問題的態度來處理多線程並發訪問控制,但缺點是只適用於對象創建后內部狀態和數據不可發生變化的情況。
Java中不變模式的實現很簡單,按照OO的思想,只需要滿足以下幾點即可:
1)將對象的所有屬性設為private final的;
2)通過final修飾class確保類不可被繼承;
3)去掉對象中的所有settXX方法;
4)有包含所有屬性的構造函數用於創建對象。
生產者-消費者模式
生產者線程向內存緩沖區提交任務,消費者線程從內存緩沖區獲取任務並進行處理。
好處在於將生產者線程和消費者線程進行解耦,優化系統整體結構,緩解性能瓶頸對系統性能的影響。
Java中,一般來說使用LinkedBlockingQueue作為上面說的“內存緩沖區”,它是阻塞型BlockingQueue的一種使用Link List的實現,它對頭和尾采用兩把不同的鎖,與ArrayBlockingQueue相比提高了吞吐量,適合於實現“生產者-消費者”模式。實現的大致思路如下:
1)創建Producer類,實現run方法用於提交任務;
2)創建Consumer類,實現run方法用於處理任務;
3)Main函數中建立緩沖區,若干個生產者,若干個消費者,創建線程池並開始使這些線程工作起來。