1. 程式人生 > >《java並發編程實戰》讀書筆記8--死鎖,性能與可伸縮性,鎖粒度鎖分解鎖分段

《java並發編程實戰》讀書筆記8--死鎖,性能與可伸縮性,鎖粒度鎖分解鎖分段

線程 com display 次數 傳遞 pan blog right 影響

第10章 避免活躍性危險

10.1 死鎖

-10.1.1 鎖順序死鎖

最簡單的一種死鎖形式:

技術分享

-10.1.2 動態的鎖順序死鎖

技術分享

可以通過下面的方法來解決:

技術分享

技術分享

技術分享

-10.1.3 在協作對象之間發生死鎖

技術分享

技術分享

技術分享

-10.1.4 開放調用

如果在調用某個方法時不需要持有鎖,那麽這種調用就被稱為開放調用。

技術分享

技術分享

-10.1.5 資源死鎖

當多個線程在相同資源上等待時,也會發生死鎖。

10.2 死鎖的避免與診斷

-10.2.1 支持定時的鎖

顯示使用Lock類中的定時tryLock功能(見13章)來代替內置鎖機制。

-10.2 通過線程轉儲信息來分析死鎖

10.3 其他活躍性危險

-10.3.1 饑餓

當線程由於無法訪問它所需要的資源而不能繼續執行時,就發生了"饑餓"。引發饑餓最常見的資源就是CPU時鐘周期。

技術分享

-10.3.2 糟糕的響應性

-10.3.3 活鎖

這種問題雖然不會阻塞線程,但也不能繼續執行,因為線程將不斷重復執行相同操作,而且總會失敗。活鎖通常發生在處理事務消息的應用程序中:如果不能成功處理某個消息,那麽消息處理機制將回滾整個事務,並將它重新放到隊列的開頭。如果消息處理器在處理某種特定類型的消息時存在錯誤並導致它失敗,那麽每當這個消息從隊列中取出並傳遞到存在錯誤的處理器時,都會發生事務回滾。由於這條消息又被放到隊列開頭,因此處理器將被反復調用,並返回相同結果。雖然處理消息的線程並沒有阻塞,但也無法繼續執行下去。要解決這種問題,需要在重試機制中引入隨機性。

第11章 性能與可伸縮性

11.1 對性能的思考

11.1.1 性能與可伸縮性

11.1.2 評估各種性能權衡因素

11.2 Amdahl定律

在增加計算機資源的情況下,程序在理論上能夠實現最高加速比,這個值取決於可並行組件與串行組件所占的比重。假定F是必須被串行執行的部分,那麽根據Amdahl定律,在包含N個處理器的機器中,最高的加速比是:

技術分享

要預測應用程序在某個多處理器系統中將實現多大的加速比,還需要找出任務中的串行部分。

技術分享

11.2.1 示例:在各種框架中隱藏的串行部分

技術分享

技術分享

11.3 線程引入的開銷

11.3.1 上下文切換

JVM和操作系統的開銷。除此之外,當一個新的線程被切換進來時,它所需要的數據可能不在當前處理器的本地緩存中因此上下文切換將導致一些緩存缺失,因而線程在首次調度運行時會更加緩慢。

11.3.2 內存同步

內存柵欄(Memory Barrier)。內存柵欄可以刷新緩存,使緩存無效,刷新硬件的寫緩沖,以及停止執行管道。現代的JVM能通過優化來去掉一些不會發生競爭的鎖,從而減少不必要的同步開銷。一些更完備的JVM能通過逸出分析(Escape Analysis)來找出不會發布到堆的本地對象引用。即使不進行逸出分析,編譯器也可以執行鎖粒度粗化(Lock Coarsening)將鄰近的同步代碼塊用一個鎖合並起來。

11.3.3 阻塞

JVM在實現阻塞行為時,可以采用自旋等待(通過循環不斷地嘗試獲取鎖,知道成功)或者通過操作系統掛起被阻塞的線程。

11.4 減少鎖的持有時間

11.4.1 縮小鎖的範圍

來個例子:

技術分享

由於在AttributeStore中只有一個狀態變量attributes,因此可以通過將線程安全性委托給其他的類來進一步提升它的性能。通過用線程安全的Map(Hashtable,SynchronousMap或ConcurrentHashMap)來代替attributes, AttributeStore可以將確保線程安全性的任務委托給頂層的線程安全容器來實現。

11.4.2 減小鎖的粒度

降低線程請求鎖的頻率,可以通過鎖分解和鎖分段等技術來實現。采用相互獨立的鎖來保護獨立的狀態變量。雖然能減小鎖操作的粒度,實現更高的伸縮性,然而,使用的鎖越多發生死鎖的風險也就越高。如果一個鎖要保護多個相互獨立的變量,那麽可以將這個鎖分解為多個鎖,並且每個鎖只保護一個變量,從而提高可伸縮性。來個例子:

技術分享

將上面的代碼改一下,不用ServerStatus鎖來保護用戶狀態和查血狀態,而是每個狀態都通過一個鎖來保護,如下面的程序所示:

技術分享

技術分享

11.4.3 鎖分段

在某些情況下,可以將鎖分解技術進一步擴展為對一組獨立對象上的鎖分解,這中情況被稱為鎖分段。

技術分享

(看起來很牛逼的樣子)。鎖分段的一個劣勢在於:與采用單個鎖來實現獨占訪問相比,要獲取多個鎖來實現獨占訪問將更加困難且開銷更高。

技術分享

技術分享

11.4.4 避免熱點域

鎖分解和鎖分段都能使不同的線程在不同的數據(或者同一個數據的不同部分)上操作,而不會相互幹擾。如果程序采用鎖分段技術,那麽一定要表現出在鎖上的競爭頻率高於在鎖保護的數據上發生競爭的頻率。

當每個操作都請求多個變量時,鎖的粒度將很難降低。這是在性能和可伸縮性之間的相互制衡的另一個方面,一些常見的優化措施,例如將一些反復計算的結果緩存起來,都會引入一些“熱點域”,而這些熱點域往往會限制可伸縮性。

11.4.5 一些替代獨占鎖的方法

第三種降低競爭鎖的影響的技術就是放棄使用獨占鎖,從而有助於使用一種友好並發的方式來管理共享狀態。例如使用並發容器,讀-寫鎖,不可變對象以及原子變量。

11.4.6 檢測cpu的利用率

11.4.7 向對象池說“不”

11.5 示例:比較Map的性能

在單線程環境下,ConcurrentHashMap的性能比同步的HashMap的性能略好一點,但在並發環境中則要好得多。在ConcurrentHashMap的實現中假設,大多是常用的操作都是獲取某個已經存在的值,因此它對各種get操作進行了優化從而提供最高的性能和並發性。在同步的Map的實現中,可伸縮性的最主要阻礙在於整個Map中只有一個鎖,因此每次只能有一個線程能夠訪問這個Map。不同的是,ConcurrentHashMap對大多數讀操作並不會加鎖,並且在寫入操作以及其他一些需要鎖的讀操作中使用了鎖分段技術。

技術分享

11.6 減少上下文切換的開銷

當任務在運行和阻塞這兩個狀態之間轉換時,就相當於一個上下問切換。在服務器應用程序中,發生阻塞的原因之一就是在處理請求時產生的各種日誌消息。為了說明如何通過減少上下文切換的次數來提高吞吐量,我們將對兩種日誌方法的調度進行分析。(這部分沒看明白在分析什麽....汗.... 先pass掉)


技術分享

《java並發編程實戰》讀書筆記8--死鎖,性能與可伸縮性,鎖粒度鎖分解鎖分段