两个常见的并发错误
作為Baeldung的編輯,我很高興與一位作者一起撰寫有關(guān)Java通用并發(fā)陷阱的文章。 這是一本不錯的書,但是假設(shè)開發(fā)人員具有一定的能力。
我已經(jīng)看到了幾件即時并發(fā)失敗的事情。 它們很容易添加到代碼中,并保證為您提供奇怪的結(jié)果。 開發(fā)人員仍會提交這些事實,這是對我們?nèi)绾螌O和并發(fā)進行教育的一種評論,如果使用不當,這將非常危險。
除了代碼審查
作為代碼審查員,這些年來我已經(jīng)開發(fā)了一些速記。 這些幫助我發(fā)現(xiàn)了在較大的代碼更改中需要更詳細地研究的區(qū)域。 它們包括紅旗的事情,我希望出問題。 訓(xùn)練自己去發(fā)現(xiàn)關(guān)鍵的反模式或潛在的反模式是一個好主意,因為它們可以是有效的代碼,但會導(dǎo)致無效的行為。
Bean中的請求狀態(tài)
在Java應(yīng)用程序中,服務(wù),控制器,處理程序和存儲庫通常是單例的。 它們是在應(yīng)用啟動時創(chuàng)建的,然后請求通常通過多個線程傳遞給它們。
考慮如下代碼:
public void processOrder(Order order) { ... currentLineItem = order.getLine( 0 ); processLineItem(); } private void processLineItem() { myService.store(currentLineItem); }在這種情況下,該類的作者已決定該對象可以記住其當前正在處理的項目,從而節(jié)省了將該項目傳遞給下一個函數(shù)的工作。
這違反了兩個原則:線程安全和有意義的對象狀態(tài)。 訂單處理者不太可能真正了解其正在處理的訂單。 您可能會想像一些有狀態(tài)地遍歷某個訂單,某種游標,閱讀器或構(gòu)建器的項目,但是將所有這些項目混合到一個對象中則很麻煩。
不過,最重要的是,有一個明確的定義可以解釋為什么這是錯誤的。 如果將請求的每個屬性放入該請求的接收者中,那么您將有兩個風險:
- 在多線程執(zhí)行中的請求之間出血
- 如果事情沒有完全整理,則在單線程的請求之間流血
簡而言之,永不做!
瘋狂的懶惰初始化
延遲初始化允許:
- 由于更快的啟動
- 必要時及時加載資源
- 如果不需要,則不加載資源(例如,無服務(wù)器Lambda,在其生命周期中可能永遠不會被要求執(zhí)行特定的代碼路徑)
- 定制如何通過較早發(fā)生的活動加載資源
所有這些都很好。 但是,此代碼:
private LazyService getLazyService() { if (lazyService != null ) { return lazyService; } LazyService newLazyService = connectToLazyService(); registerWithServiceRegistry(newLazyService); lazyService = newLazyService; return newLazyService; }盡管它可以工作,但可以同時調(diào)用并出錯。 它的錯誤程度取決于各種各樣的事情。 在示例中,我試圖暗示我們正在處理的事情:
- 在并發(fā)調(diào)用中,發(fā)生了多個延遲加載…
- ……如果這很昂貴,那是浪費
- 如果發(fā)生多個懶惰加載,則可能兩個對象在內(nèi)存中的駐留時間超過了所需時間,或者永遠存在
- 如果這是單例,則獲取孤立對象的請求可能無法與其余請求協(xié)調(diào)
- 使用手工進行的非線程安全的對象初始化真是遺憾
為了正確進行單例初始化,您應(yīng)該使用雙重檢查鎖定或使用框架,甚至明智地使用基于static字段的簡單Java單例。
其他并發(fā)失敗
以上兩個似乎是最常見的錯誤,以至于顯而易見。 如果發(fā)現(xiàn)另一個,請將其放在評論中。
翻譯自: https://www.javacodegeeks.com/2020/01/two-common-concurrency-bugs.html
總結(jié)
- 上一篇: java8 默认方法_默认方法:Java
- 下一篇: 魅族18spro参数配置详情(深度测评值