Java 多線程程序的性能調校

本文摘抄自《Java 多線程編程實戰指南》核心篇 第十二章小結

個人博客:DoubleFJ の Blog

本章介紹了與 Java 多線程程序緊密相關的性能調校常用技術。


  Java 虛擬機自 Java 6 開始對內部鎖進行了若干優化:鎖消除、鎖粗化、偏向鎖以及適應性鎖。除鎖消除是 Java 7 開始引入的,其他優化均是在 Java 6 開始引入的,這些優化僅在 Java 虛擬機的 server 模式下起作用。這些優化默認都是開啓的,且多數優化都可能依賴於 JIT 的內聯優化,並且其本身也可能是通過 JIT 編譯實現的。 因此,這些優化都有其開銷。鎖消除優化能夠徹底消除鎖的開銷,它依賴於逃逸分析技術。鎖粗化優化能夠減少線程申請/釋放鎖的頻率,其代價是使臨界區長度變大,從而可能導致線程在申請鎖時的等待時間變長。偏向鎖優化可以減小鎖的申請/釋放的開銷,它不適用於爭用程度較高的鎖。適應性鎖優化可以減小鎖申請的開銷,有利於減少上下文切換。

  鎖的開銷主要是由爭用鎖引起的。 這些開銷主要包括:上下文切換與線程調度開銷、內存同步、編譯器優化受限的開銷以及限制可伸縮性。降低鎖的開銷可以從使用鎖的替代品、降低鎖的爭用程度以及減少線程所需申請的鎖的數量這幾個方面入手。

  使用可參數化鎖可以減少線程所需申請的鎖的數量從而降低鎖的開銷,但是它在一定程度上破壞了封裝性。

  減小臨界區的長度可以減少鎖的持有時間,從而降低鎖的爭用程度。減小臨界區的長度有利於適用性鎖優化發揮作用。在不影響線程安全的前提下,將臨界區中的阻塞式 I/O 等阻塞操作以及較耗時的操作挪動到臨界區之外可以減小臨界區的長度。

  減小鎖的粒度常用技術包括鎖拆分技術和鎖分段技術。 鎖拆分技術在高爭用情況下的效果可能並不明顯;鎖分段技術會使得對整個對象進行加鎖比較困難乃至不可能。

  減少上下文切換可以從這幾個方面入手:控制線程數量、避免在臨界區中執行阻塞式 I/O 等阻塞操作、避免在臨界區中執行比較耗時的操作和減少 Java 虛擬機垃圾回收。

  運用多線程設計模式也有助於提升多線程程序的性能,但是程序的複雜性也可能相應增加。

  僞共享產生的前提是多個線程訪問被緩存到同一個緩存中的不同變量,它會導致大量的緩存未命中,從而增加內存訪問操作的開銷。 瞭解 Java 對象的內存佈局有助於分析與消除僞共享。Java 對象內存佈局的規則包括:對象是以 8 字節爲粒度進行對齊的、對象中的實例字段並非依照其源代碼聲明順序排列以及繼承自父類的實例字段不會與類本身定義的實例字段混雜在一起進行存儲等。使用 jol 工具可以查看具體對象的內存佈局情況。判斷僞共享是否存在可以從分析多個線程是否存在共同的共享變量入手,並通過 jol 以及 Linux 內核工具 perf 來進一步分析與確認。僞共享可通過手工填充、自動填充以及降低共享變量的訪問頻率這幾個方面來消除與規避。 手工填充和自動填充可以在無須調整程序算法的前提下消除僞共享。手工填充的缺點比較多,使用該方法我們必須知道緩存行的寬度、Java 對象的具體內存佈局,這使得該方法存在硬件、軟件層面的可移植性問題,並對人員的要求比較高。並且,我們還需要避免手工填充的字段被 Java 虛擬機優化掉,自動填充依賴於 @Contented 註解,它避免了手工填充的缺點,但是其消耗的額外空間更多。Java 虛擬機對自動填充的支持需要通過 Java 虛擬機的開關 “-XX:-RestrictContended” 開啓。雖然減少共享變量的訪問頻率所帶來的效果可能比較明顯,但是由於它可能涉及程序算法的調整,因此其適用範圍比較有限。

本章知識結構圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章