並發對於開發可以執行多個並發操作的健壯、可擴展的應用程式非常關鍵。然而,為此需要付出同步方面的代價。由於獲取和釋放鎖的隨之而來的開銷,它會產生性能成本。為了減輕這些效能成本,JVM 中融入了多種優化,例如偏向鎖定、鎖定消除、鎖定粗化以及輕量級和重量級鎖定的概念。
在本文中,我們將更詳細地了解這些最佳化,並探討它們如何改進多執行緒 Java 應用程式中的同步。
Java 鎖定基礎知識
在Java中,區塊或方法的同步確保一次只有一個執行緒可以執行程式碼的關鍵部分。當考慮多執行緒環境中的資源共享時,這一點尤其重要。 Java 透過依賴內在鎖定來實現這一點,或者有時,它們被稱為與物件或類別關聯的監視器,透過使用同步區塊來幫助管理對執行緒的存取。
雖然同步是執行緒安全的必要條件,但當爭用較低或完全不存在時,同步的成本可能會相當高。這就是 JVM 優化介入的地方。因此,這降低了鎖定成本並提高整體效能。
1。偏向鎖定
什麼是偏向鎖定?
偏向鎖是一種旨在減少鎖定獲取開銷的最佳化。它進行了優化,以降低鎖獲取的成本,鎖獲取的成本由單個線程主導或大部分由單個線程訪問。此類程式通常由同一執行緒取得和釋放鎖,而不會與其他執行緒發生爭用。 JVM 可以識別這種模式並將鎖偏向於該特定執行緒;接下來的鎖獲取幾乎是免費的。
偏向鎖定如何運作?
如果啟用偏向鎖定,那麼當執行緒第一次取得鎖定時,它會使該鎖定偏向於該執行緒。線程的身份記錄在鎖對象的標頭中,該線程後續的鎖獲取不涉及任何同步 - 它們只是檢查鎖是否偏向當前線程,這是一個非常快速的非阻塞操作.
如果另一個執行緒嘗試取得鎖,那麼偏向就會被取消,JVM 會退回到標準的無偏鎖機制。在此階段,它現在是一個標準鎖,第二個執行緒必須透過標準鎖定過程來取得它。
偏向鎖定的好處
效能:同一個執行緒在偏向鎖上的取得幾乎是免費鎖的取得。
因此,不需要爭用處理,因為其他執行緒沒有機會參與取得鎖定。
較低的開銷:除非發生爭用,否則不需要更改鎖定的狀態或修改與同步相關的元資料。
何時使用偏向鎖定?
偏向鎖在鎖主要由同一個執行緒存取的應用程式中非常有用,例如單執行緒應用程式或多執行緒下鎖爭用較低的應用程式。它在大多數 JVM 中預設為啟用。
如何停用偏向鎖定
偏向鎖定預設啟用,但也可以使用 JVM 標誌停用,如下所示:
-XX:-UseBiasedLocking
2。鎖定消除
什麼是鎖消除?
鎖消除是一種非常強大的最佳化,JVM 完全消除了一些不必要的同步(鎖)。它將在 JIT 編譯期間檢查程式碼是否有任何機會,其中發現同步是不必要的。當鎖僅被一個執行緒存取時,或者 JVM 將用於同步的物件在不同執行緒中不共用相同物件時,通常會發生這種情況。一旦 JVM 認為不再需要它,它就會消除鎖。
鎖消除是如何運作的?
在 JIT 編譯的逃逸分析階段,JVM 檢查物件是否僅限於單一執行緒或僅在本機上下文中使用。如果因為物件沒有逃逸創建它的執行緒的範圍而可以刪除該物件上的同步,那麼就會如此。
예를 들어, 객체가 메소드 내에서 완전히 생성되고 사용되는 경우(스레드 간에 공유되지 않음) JVM은 다른 스레드가 해당 객체에 액세스할 수 없다는 것을 인식하므로 모든 동기화가 중복됩니다. 이러한 경우 JIT 컴파일러는 단순히 잠금을 완전히 제거합니다.
잠금 오버헤드 제로: 불필요한 동기화를 제거하면 JVM이 애초에 잠금을 획득하고 해제하는 데 드는 비용을 지불하지 않아도 됩니다.
더 높은 처리량: 특히 코드에 동기화된 블록이 많이 포함된 경우 데드 동기화로 인해 애플리케이션의 처리량이 더 높아질 수 있습니다.
이 코드를 살펴보세요.
public void someMethod() { StringBuilder sb = new StringBuilder(); synchronized (sb) { sb.append("Hello"); sb.append("World"); } }
이 경우 StringBuilder는 someMethod 내에서만 사용되고 다른 스레드 간에 공유되지 않으므로 sb에 대한 동기화는 필요하지 않습니다. 이를 보면 JVM은 이스케이프 분석을 수행하여 잠금을 제거할 수 있습니다.
3. 잠금 강화
Lock Coarsening이란 무엇인가요?
잠금 조대화는 루프나 작은 코드 섹션에서 잠금을 지속적으로 획득하고 해제하는 대신 JVM이 잠금 범위를 확장하여 더 많은 코드 덩어리를 포함하는 최적화입니다.
락 조대화 작업
JVM이 긴밀한 루프 또는 인접한 여러 코드 블록이 잠금을 너무 자주 획득하고 해제하는 것을 발견하면 루프 외부 또는 여러 코드 블록에 걸쳐 잠금을 수행하여 잠금을 거칠게 할 수 있습니다. 이로 인해 무잠금 획득 및 해제 비용이 많이 들고 스레드가 더 많은 반복을 위해 잠금을 보유할 수 있습니다.
코드 예: 잠금 강화
다음 코드 조각을 고려하세요.
for (int i = 0; i < 1000; i++) { synchronized (lock) { // Do something } }
잠금 조대화는 잠금 획득을 루프 외부로 푸시하므로 스레드는 한 번만 잠금을 획득합니다.
synchronized (lock) { for (int i = 0; i < 1000; i++) { // Do something } }
JVM은 추가 잠금 획득 및 해제를 방지하여 성능을 획기적으로 향상시킬 수 있습니다.
잠금 강화 혜택
잠금 오버헤드의 자유도 감소: 축소는 특히 수천 번 반복된 루프와 같은 핫스팟 코드에서 잠금 획득 및 해제를 방지합니다.
향상된 성능:
장기간 잠그면 잠금 없이 이러한 잠금을 여러 번 획득하고 해제하는 시나리오와 비교할 때 성능이 향상됩니다.
4. 경량 및 중량 잠금장치
JVM은 스레드 간의 경합 정도에 따라 두 가지 잠금 기술을 사용합니다. 이러한 기술에는 경량 잠금 장치와 중량 잠금 장치가 포함됩니다.
경량 잠금
경량 잠금은 경합 잠금이 없을 때 발생합니다. 즉, 단 하나의 스레드만 해당 잠금을 획득하려고 합니다. 이러한 시나리오에서 JVM은 잠금을 획득하려고 할 때 CAS 작업을 사용하여 획득을 최적화하며 이는 중량 동기화 없이 발생할 수 있습니다.
헤비급 잠금
여러 스레드가 동일한 잠금을 얻으려는 경우; 즉, 경합이 있는 경우 JVM은 이를 헤비웨이트 잠금으로 에스컬레이션합니다. 여기에는 OS 수준에서 스레드를 차단하고 OS 수준 동기화 프리미티브를 사용하여 스레드를 관리하는 작업이 포함됩니다. 중량 잠금은 실제로 OS에서 컨텍스트 전환을 수행하고 스레드를 관리해야 하기 때문에 속도가 느립니다.
잠금 에스컬레이션
경량 잠금에서 경합이 발생하면 JVM이 이를 헤비급 잠금으로 에스컬레이션할 수 있습니다. 여기서 에스컬레이션은 빠른 사용자 수준 잠금에서 스레드 차단을 포함하는 더 비싼 OS 수준 잠금으로 전환하는 것을 의미합니다.
경량 잠금장치의 장점
빠른 잠금 획득: 경합이 없을 때 경량 잠금은 OS 수준 동기화를 피하기 때문에 중량 잠금보다 훨씬 빠릅니다.
차단 감소: 경합이 없으면 스레드가 차단되지 않으며 지연 시간이 줄어들면서 선형적으로 증가합니다.
무거운 자물쇠의 단점
성능 오버헤드: 중량급 잠금은 매우 높은 경합 체제에서 성능 저하와 함께 스레드 차단, 컨텍스트 전환, 스레드 깨우기 비용을 발생시킵니다.
이러한 모든 최적화는 JVM이 멀티 스레드 애플리케이션의 성능을 향상시키는 데 도움이 되므로 이제 개발자는 동기화 오버헤드를 많이 희생하지 않고도 안전한 동시 코드를 작성할 수 있습니다. 이러한 최적화를 이해하면 개발자가 특히 잠금으로 인해 성능이 저하되는 경우 더욱 효율적인 시스템을 설계하는 데 도움이 될 수 있습니다.
以上是了解 JVM 鎖優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!