首頁  >  文章  >  Java  >  Java鎖如何優化

Java鎖如何優化

PHPz
PHPz轉載
2023-05-16 08:16:051297瀏覽

  鎖定優化

#   這裡的鎖定優化主要是指 JVM 對 synchronized 的最佳化。

  自旋鎖定

#   互斥同步進入阻塞狀態的開銷都很大,應該盡量避免。在許多應用程式中,共享資料的鎖定狀態只會持續很短的一段時間。自旋鎖的想法是讓一個執行緒在請求一個共享資料的鎖時執行忙循環(自旋)一段時間,如果在這段時間內能獲得鎖,就可以避免進入阻塞狀態。

  自旋鎖雖然能避免進入阻塞狀態從而減少開銷,但是它需要進行忙循環操作佔用 CPU 時間,它只適用於共享資料的鎖定狀態很短的場景。

  在 JDK 1.6 中引入了自適應的自旋鎖。自適應意味著自旋的次數不再固定了,而是由前一次在同一個鎖上的自旋次數及鎖的擁有者的狀態來決定。

  鎖消​​除

#   鎖定消除是指對於被偵測出不可能存在競爭的共享資料的鎖定進行消除。

  鎖消​​除主要是透過逃逸分析來支持,如果堆上的共享資料不可能逃逸出去被其它線程存取到,那麼就可以把它們當成私有資料對待,也就可以將它們的鎖進行消除。

  對於一些看起來沒有加鎖的程式碼,其實隱式的加了很多鎖。例如下面的字串拼接程式碼就隱式加了鎖:

  public static String concatString(String s1, String s2, String s3) { return s1 s2 s3; }

  String 是一個不可變的類,編譯器會對 String 的拼接自動最佳化。在 JDK 1.5 之前,會轉換為 StringBuffer 物件的連續 append() 操作:

public static String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString())(s2); sb.append(s3); return sb.toString())(s2); sb.append(s3); return sb.toString() ; }

  每個 append() 方法中都有一個同步區塊。虛擬機器觀察變數 sb,很快就會發現它的動態作用域被限制在 concatString() 方法內部。也就是說,sb 的所有引用永遠不會逃逸到 concatString() 方法之外,其他執行緒無法存取到它,因此可以進行消除。

  鎖粗化

#   如果一系列的連續操作都對同一個物件反覆加鎖和解鎖,頻繁的加鎖操作就會導致效能損耗。

  上一節的範例程式碼中連續的 append() 方法就屬於這類情況。如果虛擬機器偵測到由這樣的一串零碎的操作都對同一個物件加鎖,將會把加鎖的範圍擴展(粗化)到整個操作序列的外部。對於上一節的範例程式碼就是擴展到第一個 append() 操作之前直至最後一個 append() 操作之後,這樣只需要加鎖一次就可以了。

  輕量級鎖定

# JDK 1.6 引入了偏向鎖和輕量級鎖,讓鎖擁有了四個狀態:無鎖狀態(unlocked)、偏向鎖狀態(biasble)、輕量級鎖狀態(lightweight locked)和重量級鎖定狀態( inflated)。

  重量級鎖也就是通常說synchronized的物件鎖。

  以下是 HotSpot 虛擬機器物件頭的記憶體佈局,這些資料稱為 Mark Word。其中 tag bits 對應了五個狀態,這些狀態在右側的 state 表格中給出。除了 marked for gc 狀態,其它四個狀態已經在前面介紹過了。

  下圖左側是一個執行緒的虛擬機棧,其中有一部分稱為 Lock Record 的區域,這是在輕量級鎖定運行過程中創建的,用於存放鎖定物件的 Mark Word。而右側就是一個鎖對象,包含了 Mark Word 和其它資訊。

  輕量級鎖是相對於傳統的重量級鎖而言,它使用 CAS 操作來避免重量級鎖使用互斥量的開銷。對於絕大部分的鎖,在整個同步週期內都是不存在競爭的,因此也就不需要都使用互斥量進行同步,可以先採用CAS 操作進行同步,如果CAS 失敗了再改用互斥量進行同步。

  當嘗試取得一個鎖定物件時,如果鎖定物件標記為 0 01,表示鎖定物件的鎖未鎖定(unlocked)狀態。此時虛擬機器在目前執行緒的虛擬機器堆疊中建立 Lock Record,然後使用 CAS 操作將物件的 Mark Word 更新為 Lock Record 指標。如果 CAS 操作成功了,那麼執行緒就取得了該物件上的鎖,而物件的 Mark Word 的鎖標記變成 00,表示該物件處於輕量級鎖定狀態。

如果CAS 操作失敗了,虛擬機首先會檢查對象的Mark Word 是否指向當前線程的虛擬機棧,如果是的話說明當前線程已經擁有了這個鎖對象,那就可以直接進入同步塊繼續執行,否則說明這個鎖物件已經被其他線程線程搶佔了。如果有兩條以上的線程爭用同一個鎖,那麼輕量級鎖就不再有效,要膨脹為重量級鎖。

  偏向鎖

  偏向鎖的想法是偏向於第一個獲取鎖對象的線程,這個線程在之後獲取該鎖就不再需要進行同步操作,甚至連 CAS 操作也不再需要。

  當鎖物件第一次被執行緒獲得的時候,進入偏向狀態,標示為 1 01。同時使用 CAS 操作將執行緒 ID 記錄到 Mark Word 中,如果 CAS 操作成功,這個執行緒以後每次進入這個鎖相關的同步區塊就不需要再進行任何同步操作。

  當有另一個執行緒去嘗試取得這個鎖定物件時,偏向狀態就宣告結束,此時撤銷偏向(Revoke Bias)後恢復到未鎖定狀態或輕量級鎖定狀態。

以上是Java鎖如何優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除