首頁 Java java教程 Java虛擬機器四種最佳化對內部鎖的方式解析

Java虛擬機器四種最佳化對內部鎖的方式解析

Oct 13, 2017 am 10:18 AM
java 最佳化 內部

這篇文章主要介紹了淺談Java虛擬機對內部鎖的四種優化方式,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟著小編過來看看吧

自Java 6/Java 7開始,Java虛擬機器對內部鎖的實作進行了一些最佳化。這些優化主要包括鎖定消除(Lock Elision)、鎖粗化(Lock Coarsening)、偏向鎖(Biased Locking)以及適應性鎖(Adaptive Locking)。這些最佳化僅在Java虛擬機器server模式下運作(即執行Java程式時我們可能需要在命令列中指定Java虛擬機器參數「-server」以開啟這些最佳化)。

1 鎖定消除

鎖定消除(Lock Elision)是JIT編譯器對內部鎖的具體實現所做的一種最佳化。

 

鎖定消除(Lock Elision)示意圖

在動態編譯同步區塊的時候,JIT編譯器可以藉助一種稱為逃逸分析(Escape Analysis)的技術來判斷同步區塊所使用的鎖定物件是否只能夠被一個執行緒存取而沒有被發佈到其他執行緒。如果同步區塊所使用的鎖定物件透過這種分析被證實只能夠被一個執行緒訪問,那麼JIT編譯器在編譯這個同步區塊的時候並不產生synchronized所表示的鎖的申請與釋放對應的機器碼,而僅產生原臨界區代碼對應的機器碼,這就造成了被動態編譯的字節碼就像是不包含monitorenter(申請鎖)和monitorexit(釋放鎖)這兩個字節碼指令一樣,即消除了鎖的使用。這種編譯器最佳化就被稱為鎖消除(Lock Elision),它使得特定情況下我們可以完全消除鎖的開銷。

Java標準庫中的有些類別(例如StringBuffer)雖然是線程安全的,但是在實際使用中我們往往不在多個線程間共享這些類別的實例。而這些類別在實現線程安全的時候往往借助於內部鎖。因此,這些類別是鎖消除優化的常見目標。

清單12-1  可進行鎖定消除最佳化的範例程式碼


#
public class LockElisionExample {

 public static String toJSON(ProductInfo productInfo) {
  StringBuffer sbf = new StringBuffer();
  sbf.append("{\"productID\":\"").append(productInfo.productID);
  sbf.append("\",\"categoryID\":\"").append(productInfo.categoryID);
  sbf.append("\",\"rank\":").append(productInfo.rank);
  sbf.append(",\"inventory\":").append(productInfo.inventory);
  sbf.append('}');

  return sbf.toString();
 }
}
登入後複製

在上面範例中,JIT編譯器在編譯toJSON方法的時候會將其呼叫的StringBuffer.append/toString方法內聯(Inline)到該方法之中,這相當於把StringBuffer.append/toString方法的方法體中的指令複製到toJSON方法體之中。這裡的StringBuffer實例sbf是一個局部變量,而該變數所引用的物件並沒有被發佈到其他線程,因此sbf引用的物件只能夠被sbf所在的方法(toJSON方法)的當前執行線程(一個線程)訪問。所以,JIT編譯器此時可以消除toJSON方法中從StringBuffer.append/toString方法的方法體複製的指令所使用的內部鎖定。在這個例子中,StringBuffer.append/toString方法本身所使用的鎖並不會被消除,因為系統中可能還有其他地方在使用StringBuffer,而這些程式碼可能會共用StringBuffer實例。

鎖定消除優化所依賴的逃逸分析技術自Java SE 6u23起預設是開啟的,但是鎖定消除優化是在Java 7開始引入的。

從上述例子可以看出,鎖定消除最佳化也可能需要以JIT編譯器的內聯最佳化為前提。而一個方法是否會被JIT編譯器內聯取決於該方法的熱度以及該方法對應的字節碼的尺寸(Bytecode Size)。因此,鎖消除最佳化能否被實施也取決於被呼叫的同步方法(或帶有同步區塊的方法)是否能夠被內聯。

鎖定消除優化告訴我們在該使用鎖的情況下必須使用鎖,而不必過度在意鎖的開銷。開發人員應該在程式碼的邏輯層面考慮是否需要加鎖,而至於程式碼運行層面上某個鎖是否真的有必要使用則由JIT編譯器來決定。鎖定消除最佳化並不表示開發人員在編寫程式碼的時候可以隨意使用內部鎖(在不需要加鎖的情況下加鎖),因為鎖定消除是JIT編譯器而不是javac所做的一種最佳化,而一段程式碼只有在執行的頻率夠大的情況下才有可能會被JIT編譯器最佳化。也就是說在JIT編譯器最佳化介入之前,只要原始碼中使用了內部鎖,那麼這個鎖的開銷就會存在。另外,JIT編譯器所執行的內嵌最佳化、逃逸分析以及鎖定消除最佳化本身都是有其開銷的。

在鎖消除的作用下,利用ThreadLocal將一個線程安全的物件(例如Random)作為一個線程特有物件來使用,不僅可以避免鎖的爭用,還可以徹底消除這些物件內部所使用的鎖的開銷。

2 鎖粗化

鎖定粗化(Lock Coarsening/Lock Merging)是JIT編譯器對內部鎖的具體實作所做的最佳化。

 

鎖定粗化(Lock Coarsening)示意圖

对于相邻的几个同步块,如果这些同步块使用的是同一个锁实例,那么JIT编译器会将这些同步块合并为一个大同步块,从而避免了一个线程反复申请、释放同一个锁所导致的开销。然而,锁粗化可能导致一个线程持续持有一个锁的时间变长,从而使得同步在该锁之上的其他线程在申请锁时的等待时间变长。例如上图中,第1个同步块结束和第2个同步块开始之间的时间间隙中,其他线程本来是有机会获得monitorX的,但是经过锁粗化之后由于临界区的长度变长,这些线程在申请monitorX时所需的等待时间也相应变长了。因此,锁粗化不会被应用到循环体内的相邻同步块。

相邻的两个同步块之间如果存在其他语句,也不一定就会阻碍JIT编译器执行锁粗化优化,这是因为JIT编译器可能在执行锁粗化优化前将这些语句挪到(即指令重排序)后一个同步块的临界区之中(当然,JIT编译器并不会将临界区内的代码挪到临界区之外)。

实际上,我们写的代码中可能很少会出现上图中那种连续的同步块。这种同一个锁实例引导的相邻同步块往往是JIT编译器编译之后形成的。

例如,在下面的例子中

清单12-2 可进行锁粗化优化的示例代码


public class LockCoarseningExample {
 private final Random rnd = new Random();

 public void simulate() {
  int iq1 = randomIQ();
  int iq2 = randomIQ();
  int iq3 = randomIQ();
  act(iq1, iq2, iq3);
 }

 private void act(int... n) {
  // ...
 }

 // 返回随机的智商值
 public int randomIQ() {
  // 人类智商的标准差是15,平均值是100
  return (int) Math.round(rnd.nextGaussian() * 15 + 100);
 }
 // ...
}
登入後複製

simulate方法连续调用randomIQ方法来生成3个符合正态分布(高斯分布)的随机智商(IQ)。在simulate方法被执行得足够频繁的情况下,JIT编译器可能对该方法执行一系优化:首先,JIT编译器可能将randomIQ方法内联(inline)到simulate方法中,这相当于把randomIQ方法体中的指令复制到simulate方法之中。在此基础上,randomIQ方法中的rnd.nextGaussian()调用也可能被内联,这相当于把Random.nextGaussian()方法体中的指令复制到simulate方法之中。Random.nextGaussian()是一个同步方法,由于Random实例rnd可能被多个线程共享(因为simulate方法可能被多个线程执行),因此JIT编译器无法对Random.nextGaussian()方法本身执行锁消除优化,这使得被内联到simulate方法中的Random.nextGaussian()方法体相当于一个由rnd引导的同步块。经过上述优化之后,JIT编译器便会发现simulate方法中存在3个相邻的由rnd(Random实例)引导的同步块,于是锁粗化优化便“粉墨登场”了。

锁粗化默认是开启的。如果要关闭这个特性,我们可以在Java程序的启动命令行中添加虚拟机参数“-XX:-EliminateLocks”(开启则可以使用虚拟机参数“-XX:+EliminateLocks”)。

3 偏向锁

偏向锁(Biased Locking)是Java虚拟机对锁的实现所做的一种优化。这种优化基于这样的观测结果(Observation):大多数锁并没有被争用(Contented),并且这些锁在其整个生命周期内至多只会被一个线程持有。然而,Java虚拟机在实现monitorenter字节码(申请锁)和monitorexit字节码(释放锁)时需要借助一个原子操作(CAS操作),这个操作代价相对来说比较昂贵。因此,Java虚拟机会为每个对象维护一个偏好(Bias),即一个对象对应的内部锁第1次被一个线程获得,那么这个线程就会被记录为该对象的偏好线程(Biased Thread)。这个线程后续无论是再次申请该锁还是释放该锁,都无须借助原先(指未实施偏向锁优化前)昂贵的原子操作,从而减少了锁的申请与释放的开销。

然而,一个锁没有被争用并不代表仅仅只有一个线程访问该锁,当一个对象的偏好线程以外的其他线程申请该对象的内部锁时,Java虚拟机需要收回(Revoke)该对象对原偏好线程的“偏好”并重新设置该对象的偏好线程。这个偏好收回和重新分配过程的代价也是比较昂贵的,因此如果程序运行过程中存在比较多的锁争用的情况,那么这种偏好收回和重新分配的代价便会被放大。有鉴于此,偏向锁优化只适合于存在相当大一部分锁并没有被争用的系统之中。如果系统中存在大量被争用的锁而没有被争用的锁仅占极小的部分,那么我们可以考虑关闭偏向锁优化。

偏向锁优化默认是开启的。要关闭偏向锁优化,我们可以在Java程序的启动命令行中添加虚拟机参数“-XX:-UseBiasedLocking”(开启偏向锁优化可以使用虚拟机参数“-XX:+UseBiasedLocking”)。

4 适应性锁

适应性锁(Adaptive Locking,也被称为 Adaptive Spinning )是JIT编译器对内部锁实现所做的一种优化。

存在锁争用的情况下,一个线程申请一个锁的时候如果这个锁恰好被其他线程持有,那么这个线程就需要等待该锁被其持有线程释放。实现这种等待的一种保守方法——将这个线程暂停(线程的生命周期状态变为非Runnable状态)。由于暂停线程会导致上下文切换,因此对于一个具体锁实例来说,这种实现策略比较适合于系统中绝大多数线程对该锁的持有时间较长的场景,这样才能够抵消上下文切换的开销。另外一种实现方法就是采用忙等(Busy Wait)。所谓忙等相当于如下代码所示的一个循环体为空的循环语句:


// 当锁被其他线程持有时一直循环 
while (lockIsHeldByOtherThread){}
登入後複製

可见,忙等是通过反复执行空操作(什么也不做)直到所需的条件成立为止而实现等待的。这种策略的好处是不会导致上下文切换,缺点是比较耗费处理器资源——如果所需的条件在相当长时间内未能成立,那么忙等的循环就会一直被执行。因此,对于一个具体的锁实例来说,忙等策略比较适合于绝大多数线程对该锁的持有时间较短的场景,这样能够避免过多的处理器时间开销。

事实上,Java虚拟机也不是非要在上述两种实现策略之中择其一 ——它可以综合使用上述两种策略。对于一个具体的锁实例,Java虚拟机会根据其运行过程中收集到的信息来判断这个锁是属于被线程持有时间“较长”的还是“较短”的。对于被线程持有时间“较长”的锁,Java虚拟机会选用暂停等待策略;而对于被线程持有时间“较短”的锁,Java虚拟机会选用忙等等待策略。Java虚拟机也可能先采用忙等等待策略,在忙等失败的情况下再采用暂停等待策略。Java虚拟机的这种优化就被称为适应性锁(Adaptive Locking),这种优化同样也需要JIT编译器介入。

适应性锁优化可以是以具体的一个锁实例为基础的。也就是说,Java虚拟机可能对一个锁实例采用忙等等待策略,而对另外一个锁实例采用暂停等待策略。

从适应性锁优化可以看出,内部锁的使用并不一定会导致上下文切换,这就是我们说锁与上下文切换时均说锁“可能”导致上下文切换的原因。

 

以上是Java虛擬機器四種最佳化對內部鎖的方式解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

創造未來:零基礎的 Java 編程 創造未來:零基礎的 Java 編程 Oct 13, 2024 pm 01:32 PM

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

See all articles