1. 程式人生 > 其它 >Java多執行緒程式設計實戰指南 設計模式 讀書筆記

Java多執行緒程式設計實戰指南 設計模式 讀書筆記

執行緒設計模式在按其有助於解決的多執行緒程式設計相關的問題可粗略分類如下。

  • 不使用鎖的情況下保證執行緒安全: Immutable Object(不可變物件)模式、Thread Specific Storage(執行緒特有儲存)模式、Serial Thread Confinement(序列執行緒封閉)模式。
  • 優雅地停止執行緒:Two-phase Termination(兩階段終止)模式。
  • 執行緒協作:Guarded Suspension(保護性暫掛)模式 、Producer-Consumer(生產者/消費者)模式。
  • 提高併發性(Concurrency)、減少等待:Promise(承諾)模式、Active Object(主動物件)模式、Pipeline(流水線)模式。
  • 提高響應性( Responsiveness ) : Master-Slave(主僕)模式、Half-sync/Half-async(半同步/半非同步)模式。
  • 減少資源消耗:Thread Pool(執行緒池)模式:Serial Thread Confinement(序列執行緒封閉)模式。

Immutable Object (不可變物件)模式

別名

該模式也被稱為Immutable (不可變)模式。

背景

多個執行緒共享一個物件的例項。

問題

當被共享的物件相應的現實世界實體的狀態變更時,系統對此要有所反映。但是,通過直接更改該被共享的物件的狀態來反映這個變更通常會導致鎖的引入以保證執行緒安全。

解決方案

將相應現實世界實體建模為狀態不可變的物件。當相應實現世界實體的狀態變更時,系統通過建立新的物件例項來反映這種狀態變更,而不是更改物件本身的狀態。

結果

多個執行緒可以在不使用鎖的情況下,以執行緒安全的方式去訪問共享物件。可能導致頻繁的物件建立。

相關模式

Thread Specific Storage模式和Serial Thread Confinement模式也可以在不引入鎖的情況下確保執行緒安全。

Guarded Suspension(保護性暫掛)模式別名

別名

該模式也被稱為Guarded Waits(受保護等待)模式。

背景

一個方法欲執行的操作(目標動作)所需的前提條件可能暫時無法滿足而稍後可能得以滿足。

問題

多執行緒環境中,某個物件的方法被呼叫時,該方法欲執行的操作所需的狀態暫時沒有得到滿足,而稍後可能得以滿足。因此,此時如果該方法返回或者丟擲異常,則會迫使客戶端程式碼對其不期望的結果進行處理。

解決方案

多執行緒環境中,當某個物件的方法(受保護方法)執行其欲執行的操作所需的狀態(保護條件)未被滿足時,將當前執行緒暫掛直到其他執行緒改變了該物件的狀態使得保護條件得以滿足時,被暫掛的執行緒得以喚醒。

結果

  • 使應用程式避免了樣板式(Boilerplate)程式碼。
  • 實現了關注點分離(Separation of Concern) 。
  • 可能增加JVM垃圾回收的負擔。
  • 可能增加上下文切換(Context Switch) 。

相關模式

Promise模式(第6章)和Producer-Consumer模式實現過程中可能需要使用GuardedSuspension模式。

Two-phase Termination(兩階段終止)模式

別名

背景問題

守護執行緒(Dacmon Thread)不會阻止JVM正常關閉,而使用者執行緒會阻止JVM正常關閉。因此,正常關閉JVM時需要先將使用者執行緒停止。但是,停止一個使用者執行緒時,我們希望該執行緒能夠在其處理完待處理的任務後再行停止。

解決方案

將執行緒的停止分為兩個階段:準備階段和執行階段。準備階段主要實現執行緒停止的標誌的設定,執行階段主要實現執行緒停止標誌的檢測並在執行緒處理完待處理的任務後停止執行緒。

結果

  • 實現了執行緒的優雅停止:執行緒可以在其處理完待處理的任務後再行停止,而非粗暴地停止。
  • 可能延遲執行緒的停止: 待停止的執行緒可能是在其處理完待處理的任務後再停止的,而這可能需要一定的等待時間。

相關模式

Producer-Consumer模式(第7章)和Master-Slave模式(第12章)可能需要使用Two-phaseTermination模式以實現其工作者執行緒的停止。

Promise(承諾)模式

別名

該模式也被稱為Future(期貨)模式。

背景

一個物件需要使用另外一個物件的某個方法(以下稱為目標方法)的返回值。

問題

目標方法需要消耗較長的處理時間才能返回表示其處理結果的值。在該方法返回之前,客戶端程式碼會被阻塞而無法進行其他處理。

解決方案

使用非同步程式設計,將目標方法的返回值改為一個憑據物件,而不是表示目標方法真正處理結果的物件(結果物件)。客戶端程式碼通過呼叫憑據物件的某個方法來獲取目標方法的結果物件。在此基礎上,採用專門的工作者執行緒或者執行緒池去執行目標方法所進行的計算。

結果

  • 既發揮了非同步程式設計的優勢——增加系統的併發性,減少不必要的等待,又保持了同步程式設計的簡單性——客戶端程式碼的編寫方式與同步程式設計無本質差別。
  • 一定程度上遮蔽了非同步程式設計和同步程式設計的差異:無論目標方法是非同步方法還是同步方法,它都不影響客戶端程式碼的編寫方式。

相關模式

目標方法可以看成Factory Method模式"中的工廠方法。

憑據物件用於獲取結果物件的方法可能需要等待目標方法對應的計算完成才能返回,這可以使用Guarded Suspension模式(第4章)來實現。Active Object模式可以看成是包含了Promise模式的複合模式,其非同步方法的返回值就是一個憑據物件。

Producer-Consumer (生產者/消費者)模式

別名

背景

資料(任務)的提供方的處理能力(速率)與相應的使用方的處理能力(速率)不均衡,或者我們不希望二者的處理能力(速率)相互影響。

問題

資料(任務)的提供方和使用方執行在同一個執行緒中會導致一方處理能力(速率)的大小對另外一方產生影響,即造成等待。

解決方案

在資料(任務)的提供方和使用方之間引入一個作為緩衝區的通道,從而使資料(任務)的提供方和使用方可以執行在各自的執行緒之中。

結果

資料(任務)的提供方和使用方的處理能力相對來說互不影響。

關注點分離(Separation of Concern):資料(任務)的提供方只需要將資料(任務)存入通道即可,它無須關心誰對資料(任務)進行處理;

而資料(任務)的使用方只需要從通道中取出資料(任務)進行處理而無須關心是誰將其存入通道的。

相關模式

許多模式可看成Producer-Consumer模式的一個例項。

Active Object(主動物件)模式

別名

該模式也被稱為Concurrent Object模式。

背景

客戶端程式碼需要訪問獨立的執行緒控制( Thread of Control)物件。

問題

客戶端程式碼需要使用某個類提供的服務,但是不希望等待相應的服務呼叫完成後才能繼續其他處理,以避免響應性和吞吐率受此服務呼叫的影響。

解決方案

將服務方法的呼叫 (Invocation)和執行(Execution)進行解耦(Decoupling)。客戶端程式碼呼叫某個服務方法時,該方法並不立即執行相應的服務操作,而是生成表示相應服務操作的物件(任務)並將其存入快取區,由專門的工作者執行緒取緩衝區中的任務進行執行。

結果

有利於提高併發性,從而提高系統的吞吐率。使除錯變得複雜。

相關模式

Active Object模式可看成Producer-Consumer模式的一個例項。
Active Object模式使用了Promise模式以實現客戶端程式碼獲取非同步任務的處理結果。

Thread Pool(執行緒池)模式

別名

背景

多執行緒環境中,新的任務不斷產生。

問題

為每個新的任務都建立一個執行緒去負責處理過程會導致執行緒不斷地被建立和銷燬,這會增加系統的資源消耗和上下文切換。

解決方案

將待處理的任務存入緩衝區,並建立一定數量的工作者執行緒,複用這些工作者執行緒使其從緩衝區中取出任務執行。

結果

  • 抵消執行緒建立的開銷,提高系統的響應性。封

  • 裝了工作者執行緒生命週期管理。

  • 減少銷燬執行緒的開銷。

  • 不恰當的使用可能導致死鎖。

相關模式

Thread Pool模式可看成Producer-Consumer模式的一個例項。

Thread Pool模式可以使用Two-phase Termination模式來實現其工作者執行緒的停止。

Thread Specific Storage(執行緒特有儲存)模式

別名

該模式也被稱為Thread Local Storage模式。

背景

多個執行緒需要訪問同一個非執行緒安全物件。或者,使用執行緒安全的物件,但希望能夠避免其使用的鎖的開銷。

問題

多個執行緒訪問同一個非執行緒安全物件(TSObject)可能產生執行緒安全問題,而我們又不希望因此而引入鎖,以便能夠避免鎖的開銷和相關問題。

解決方案

使每個執行緒獲得一個(且僅一個)該執行緒所特有的 TSObject 例項,各個執行緒僅訪問各自的TsObject例項,一個TSObject例項不會被多個執行緒共享。

結果

  • 在不引入鎖的情況下實現了對非執行緒安全物件訪問的執行緒安全。·易於使用。
  • 隱藏了系統結構,可能使系統難於理解。
  • 鼓勵了全域性物件的使用不恰當的使用可能導致記憶體洩漏。

相關模式

lmmutable Object模式和Serial Thread Confinement模式也能夠在不引入鎖的情況下確保執行緒安全。

Serial Thread Confinement(序列執行緒封閉)模式

別名

背景

非同步程式設計中,工作者執行緒需要訪問非執行緒安全物件,而我們又不希望因此而引人鎖。

問題

系統對某種併發任務的處理涉及非執行緒安全物件的訪問,而我們又不希望因此而引入鎖,以便能夠避免鎖的開銷和相關問題。

解決方案

將併發任務通過佇列序列化,再建立唯一的一個工作者執行緒對佇列中的任務進行執行。

結果

  • 在不引入鎖的情況下實現了對非執行緒安全物件訪問的執行緒安全。

  • 如果客戶端程式碼關心任務的處理結果,那麼可能導致多個客戶端執行緒等待同一個工作者執行緒的處理結果。

相關模式

Serial Thread Confinement模式可著成Producer-Consumer模式的一個例項。Immutable Object模式和Thread Specific Storage模式也能夠在不引入鎖的情況下確保執行緒安全。

如果客戶端程式碼關心任務的處理結果,那麼我們可以借用Promise模式來實現這點。

Master-Slave (主僕)模式

別名

該模式也被稱為Boss-Worker(老闆-夥計)模式。

背景

一個任務被分解為等同語義(Semantically-identical)的若干個子任務。

問題

分而治之(Divide and Conquer)是解決許多問題的一個通用原則。將一個任務(原始任務)分解為若干個子任務,再讓這些子任務獨立執行。然後將各個子任務的處理結果組合成原始任務的處理結果。這個過程需要處理好以下幾個方面。
客戶端程式碼不應該知道其呼叫的服務是基於分而治之的計算。
無論是客戶端程式碼還是子任務,它們都應該不依賴於任務分解和子任務處理結果合併的演算法。

解決方案

在服務的客戶端程式碼和子任務的處理之間引入一個協調性的物件(即 Master)。有關分而治之的相關細節被封裝在Master裡面。各個子任務由專門的工作者執行緒負責處理。

結果

  • 提升了計算效能:子任務可以並行執行。
  • 可交換性(Exchangeability)和可擴充套件性(Extensibility):替換某個Slave例項、增加一個Slave例項對Master參與者產生的影響很小。

相關模式

Master-Slave模式可看成Producer-Consumer模式的一個例項。

Master-Slave模式中的Master參與者可能會使用Promise模式以獲取子任務的處理結果。

Pipeline(流水線)模式

別名

背景

多執行緒程式設計中,規模較大的任務的處理可以分解為若干個存在依賴關係的子任務。

問題

規模較大的任務(原始任務)的處理可能比較耗時。如果對原始任務進行縱向分解,即分解得來的子任務中的每個任務的處理又包括若干個步驟,那麼即使我們採用若干個工作者執行緒去負責執行子任務的執行也仍然避免不了一個子任務的處理中所出現的等待(一個處理步驟的開始要等待前一個處理步驟的完成)。

解決方案

對原始任務進行橫向分解,即將一個任務的處理分解為若干個處理階段(Stage),其中每個處理階段的輸出作為下一個處理階段的輸入,並且各個處理階段都有相應的工作者執行緒去執行相應計算。

結果

  • 可以對有依賴關係的任務實現並行處理。
  • 為區域性使用單執行緒模型程式設計提供了便利。
  • 具備任務處理邏輯安排的靈活性。

相關模式

Pipeline模式中的處理階段可能會使用Serial Thread Confinement模式,以實現任務處理的執行緒安全。
Pipeline模式可以藉助Master-Slave模式實現某個處理階段的並行處理。

Half-sync/Half-async(半同步/半非同步)模式

別名

背景

某計算同時涉及低階(或耗時較短)任務和高階(或耗時較長)任務。

問題

低階(或耗時較短)的任務可以直接在客戶端執行緒中執行,但是高階(或耗時較長)任務在客戶端執行緒中執行則會增加客戶端執行緒的等待從而減少吞吐率並降低響應性。

解決方案

採用分層架構。將低階(或耗時較短)任務放在非同步層由客戶端執行緒執行,高階(或耗時較長)任務放在同步層由專門的後臺工作者執行緒執行。非同步層和同步層不直接通訊,而是通過佇列層進行通訊。

結果

  • 既發揮了非同步程式設計的優勢——增加系統的併發性,減少不必要的等待,又保持了同步程式設計的簡單性。
  • 各層程式碼可以使用獨立的併發訪問控制策略。

相關模式

Half-sync/Half-async模式可看成Producer-Consumer模式的一個例項。
Half-sync/Half-async模式可能會使用Two-phase 'Termination模式來停止後臺工作者執行緒。
Half-sync/Half-async模式的佇列層和同步層合起來可以使用Active Object模式來實現。
Thread Pool模式可以用來實現同步層任務的執行。

模式和模式之間的聯絡

設計模式並不是孤立的,一個設計模式往往和其他設計模式存在某些關聯,如圖所示。

設計模式之間的關係可以概括為:支援、變體、組合和備選這4種關係。

  • 支援(Support)。具體的一個設計模式能夠解決特定的問題,但是應用特定的設計模式本身也會引人特定的問題,而這些問題往往可以使用另外一些設計模式來解決,即在此情景下一個設計模式為另外一個設計模式解決其面臨的問題提供了支援。這好比一種藥物可以治療某種疾病,但是它本身又會對人體產生一些副作用(如傷害肝臟),因此醫生為病人開相應藥物的時候又開了另外用於抵消該藥物副作用的其他藥物。例如,使用Master-Slave模式可以提升服務的響應性,但其應用本身也會帶來一些問題:Master-Slave模式中,Master參與者需要獲取由各個Slave參與者例項的工作者執行緒執行的子任務的處理結果,而這點可以通過使用 Promise模式來實現。另外,Master參與者需要提供一個用於停止各個Slave參與者的工作者執行緒的介面(方法),該介面的實現可以藉助Two-phase Termination模式。
  • 變體(Variant)。在特定的情況下,一個設計模式可以看作另外一個設計模式的特例。這好比正方體可以看作長和寬相等的“特殊”長方體,這裡我們可以說正方體是長方體的一個變體,相應的“特定情況”是“長和寬相等"。例如,就Thread Pool模式(第9章)而言,當其最大執行緒池大小為Ⅰ時,相應的Thread Pool 模式實現就等效於一個Scrial ThreadConfinement模式(第11章)實現。此時,這兩個模式無論是從架構上看還是從解決的問題(執行緒安全問題)上看都是相似的。
  • 組合(Combination)。俗話說“一把鑰匙開一把鎖”。如果我們把一個具體的設計模式比作一把鎖,而把我們要解決的問題比作要開的門,那麼我們不能指望用一把鎖來開啟所有要開啟的門。在實際解決問題的過程中,我們往往需要使用多個設計模式。在此情景下,涉及的各個設計模式之間的關係即為組合關係,即它們在特定場景下組合在一起來解決問題。例如,本書第10章(Thread Specific Storage模式)提到的實戰案例中的驗證碼簡訊問題,生成隨機驗證碼的時候我們使用了Thread Specific Storage模式以避免共享ecureRandom例項可能造成不必要等待的問題;另外,將驗證碼通過簡訊傳送到使用者手機的時候,我們使用了Thread Pool模式(第9章)以避免在併發使用者量大的時候下發大量驗證碼簡訊導致系統建立的負責簡訊傳送的執行緒過多。
  • 備選(Alternative)。俗話說“條條大路通羅馬”。一個(類)特定的問題往往可以採用不同的方法來解決。不同的方法採用不同的方式去解決、各自有其適用的條件和場景。例如,多執行緒程式設計經常需要解決的一個問題是執行緒安全的問題。本書提供了可以解決該問題的3個設計模式:Immutable Object模式 、Thread Specific Storage模式和SerialThread Confinement模式。也就是說,對於執行緒安全問題的解決,我們有3個設計模式可選擇。那麼,這3個設計模式此時就形成了備選關係。