【Java】從synchronized鎖優化來了解自適應自旋鎖、鎖消除和鎖粗化


背景

在jdk1.6之前,【synchronized】是一直都被稱為重量級鎖;但是在jdk1.6之后,【synchronized】進行了各種優化,本文主要介紹一下以下三種鎖:

  • 自適應自旋鎖
  • 鎖消除
  • 鎖粗化

 

自旋鎖和自適應自旋鎖

  • 自旋鎖

 

互斥同步進入阻塞狀態的開銷都很大,應該盡量避免。

大多數情況下,共享數據的鎖定狀態持續時間很短,比如:一段邏輯運行完只需要0.0001秒,但是如果使用互斥同步方式,在線程切換上下文時反而會更加耗時。

然而自旋鎖的思想是讓一個線程在請求一個共享數據的鎖時,執行忙循環(自旋),並不會讓出CPU的使用,

如果在這段時間可以獲得所,就可以避免進入阻塞狀態,從而避免上下文的切換。

在jdk1.4中,synchronized自旋鎖是默認關閉狀態的,並且默認循環次數為10,也就是說

 

但是自旋鎖還有一個這樣的弊端:

如果鎖被占用很長時間,那么進行忙循環操作占用CPU時間很長,就會造成很大的性能開銷,

所以自旋鎖只用於共享數據的鎖定狀態很短的場景。

 

結合synchronized說一下jdk的自旋鎖:

自旋鎖在JDK 1.4中引入,默認關閉,但是可以使用【-XX:+UseSpinning】開開啟,在JDK1.6中默認開啟。同時自旋的默認次數為10次,可以通過參數【-XX:PreBlockSpin】來調整; 
如果通過參數-【XX:preBlockSpin】來調整自旋鎖的自旋次數,會帶來諸多不便。

假如:我將參數調整為10,但是系統很多線程都是等你剛剛退出的時候就釋放了鎖(假如你多自旋一兩次就可以獲取鎖),這特么有點尷尬啊~

於是JDK1.6引入自適應的自旋鎖,讓虛擬機會變得越來越聰明。

 

  • 自適應自旋鎖
JDK1.6引入了自適應自旋鎖,所謂自適應自旋鎖,就意味着自旋的次數不再是固定的,具體規則如下:

自旋次數通常由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態決定。如果線程【T1】自旋成功,自旋次數為17次,那么等到下一個線程【T2】自旋時,也會默認認為【T2】自旋17次成功,

如果【T2】自旋了5次就成功了,那么此時這個自旋次數就會縮減到5次。

自適應自旋鎖隨着程序運行和性能監控信息,從而使得虛擬機可以預判出每個線程大約需要的自旋次數

 

鎖消除

  • 話不多說,先擼一段代碼看看先:
public void add(String str1){
         StringBuffer sb = new StringBuffer("hello");
         sb.append(str1);
}

 

鎖消除,即去除不可能存在共享資源競爭的鎖。

眾所周知,【StringBuffer】是線程安全的,因為內部的關鍵方法都是被synchronized修飾過的,

但是上述代碼中,sb是局部變量,不存在競爭共享資源的現象,此時JVM會自動需要【StringBuffer】中的鎖。

 

鎖粗化

  • 先看兩個實例:
#第一種加鎖方式

public
class SynchronizedTest { private int count; public void test() { System.out.println("test"); int a = 0; synchronized (this) { count++; } } }

 

#第二種加鎖方式
public class SynchronizedTest {
    
    private int count;
    
    public void test() {
        synchronized (this) {
            System.out.println("test");
            int a = 0;
            count++;
        }
        
    }

}

通常我們為了降低鎖粒度,會選擇第一種加鎖方式,僅在線程共享資源處加鎖,從而使得同步需要的操作數量更少。

 

  • 而鎖粗化思想,就是擴大加鎖范圍,避免反復加鎖,我們再看一下【StringBuffer】的例子:
public String test(String str){
       
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():

}

由於【StringBuffer】是在內部方法實現的【synchronized】加鎖,我們無法把鎖提取到循環體外,如果沒有鎖粗化,此處要進行100次加鎖。

此時JVM會檢測到這樣一連串的操作都對同一個對象加鎖,JVM就會將鎖的范圍粗化到這一連串的操作的外部(比如while的虛幻體外),使得這一連串的操作只需要加一次鎖即可。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM