多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

大家好,今天我們給大家介紹一個多線程設計模式的一個概念,我們平時業務代碼寫得比較多,因此,如果剛上手寫比較複雜多線程代碼,很有可能會埋下一些坑,而這些坑一時之間都是很難發現,需要經過嚴格測試,甚至上線運行之後纔會在生產環境顯現出來。大家應該聽過面向對象編程的23種設計模式吧,它就是在特定場景下提供針對某一問題的可複用解決方案,而多線程設計模式是在多線程編程領域的設計模式。今天給大家介紹其中一個設計模式:Guarded Suspension(保護性暫掛模式)。

Guarded Suspension主要是用來解決線程協作的一些問題,其核心思想是某個線程執行特定的操作前需要滿足一定條件,條件未滿足則暫掛線程,處於WAITING狀態,直到條件滿足該線程繼續執行。說到這裏,大家是不是想到了wait/notify了,是的,線程的掛起和喚醒功能可以直接使用wait/notify直接實現,但除非是這方面的熟手,不然總會因爲忽略了一些技術細節而犯錯,而且這些重複代碼散落在系統各處,往往增加了維護成本,提高了出錯的概率。大家現在還能快速回憶起wait/notify的一些值得注意的編程細節嗎?

比如:

最著名的是線程過早喚醒問題,當一個線程由於調用了notifyAll而醒來時,並不意味着它的保護條件是成立的,其中有各種原因,如wait方法可以“假裝”返回;從線程被喚醒到wait重新獲取鎖的時間段內,其他線程已獲取了鎖並修改了保護條件中的狀態;由於一個條件隊列與多個保護條件相關,假設A在條件隊列等待保護條件a,當B線程因爲同一條件隊列相關的另一個保護條件b變成真,就會調用notifyAll或者notify,喚醒了A線程,但該線程相關的保護條件a並沒有成真。

因此,每次線程從wait中喚醒時,都必須再次測試保護條件是否成立,我們通常在一個循環中調用wait,相關代碼的標準形式如下:

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

另外在實現的過程中,還有信號丟失、內存可見性、鎖泄漏等各種技術細節需要我們把控,而Guarded Suspension 幫助我們把這些技術細節封裝起來,統一處理,增強了代碼的可複用性和可維護性。

現在來看下面這段簡單的代碼,描述的主要是點外賣的一個邏輯,外賣沒送到之前,我們一直處於等待狀態,等外賣送到,我們收到通知,就可以開吃了,我們總是避免不了去實現wait/notify一類的代碼:

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

重點關注eat()和foodGuy(),在方法內部實現了wait/notify,而通常這容易犯錯,有什麼辦法能將這些技術細節封裝起來,而我們平時只要實現一些業務邏輯就可以了呢?Guarded Suspension給我們提供了一個思路,它指定了幾個角色,讓這些角色各司其職,而這些角色中,有些是需要開發者實現接口,有些則是可複用的代碼。我們再來瀏覽下面這段代碼,這裏,開發者不需要實現關於wait/notify的技術細節,所有這些都封裝在了Blocker中。

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

這裏應用開發者需要實現三個角色:

GuardedObject: 這裏就是指內部類Helper,包含了受保護方法eat()和改變GuardedObject實例狀態的方法foodArrived()。

ConcretePredicate:實現具體的保護條件,這裏是

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

我們看到,有關wait/notify的代碼都被封裝在了Broker中,而其中的Blocker接口,可以我們自己實現,也可以使用已有實現,這裏的實現是ConditionVarBlocker類,它是基於Condition類和ReentrantLock類實現的, 上面的例子用到了callWithGuard和signalAfter兩方法,分別接收由應用開發者實現的GuardedAction和stateOperation,前者用於執行帶保護條件的目標動作,後者用於更改狀態動作的執行。

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

多線程設計模式解讀1—Guarded Suspension(保護性暫掛模式)

這裏注意,如果你想使用指定的Lock實例,可以在ConditionVarBlocker傳入一個,而不要在外部使用,避免不必要的嵌套同步。

你可以嘗試着自己實現一個Broker,這似乎是一勞永逸的事情。這裏補充一個小知識點,就是Condition與wait/notify的區別。每個對象都可以作爲一個鎖,而每個對象也同樣可以作爲一個條件隊列,它使得一組線程能通過某種方式等待特定的條件成真,就像一個條件對列和一個內置鎖(synchronized)關聯一樣,每一個Condition都和一個Lock關聯,它提供了比內置條件隊列更豐富的功能,如條件隊列可以是中斷或不可中斷的,基於時限的等待。

另外,一個內置鎖只能有一個相關聯的條件隊列,多個線程可能在同一個條件隊列上等待不同的保護條件,並且在最常見的加鎖模式下公開條件隊列對象,這使得我們notifyAll時無法滿足所有等待線程爲同一類型的需求,而對於Lock,可以有任意數量的Condition對象,這樣就可以將保護條件分開放到多個等待線程集中,更容易滿足單次通知的要求。在Condition對象中,與wait,notify,notifyAll方法對應的分別是await,signal,signalAll。

參考資料:

《java多線程編程實戰指南—設計模式篇》

《圖解多線程設計模式》

《java併發編程實戰》

相关文章