欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?ynchronized重量級鎖的核心原理詳解

 更新時間:2022年03月01日 16:45:34   作者:小小茶花女  
這篇文章主要為大家詳細(xì)介紹了Java?ynchronized重量級鎖的核心原理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

在JVM中,每個對象都關(guān)聯(lián)一個監(jiān)視器,這里的對象包含Object實例和Class實例。監(jiān)視器是一個同步工具,相當(dāng)于一個許可證,拿到許可證的線程即可進(jìn)入臨界區(qū)進(jìn)行操作,沒有拿到則需要阻塞等待。重量級鎖通過監(jiān)視器的方式保障了任何時間只允許一個線程通過受到監(jiān)視器保護(hù)的臨界區(qū)代碼。

1. monitor原理

jvm中每個對象都會有一個監(jiān)視器Monitor,監(jiān)視器和對象一起創(chuàng)建、銷毀。監(jiān)視器相當(dāng)于一個用來監(jiān)視這些線程進(jìn)入的特殊房間,其義務(wù)是保證(同一時間)只有一個線程可以訪問被保護(hù)的臨界區(qū)代碼塊。

每一個鎖都對應(yīng)一個monitor對象,在HotSpot虛擬機中它是由ObjectMonitor實現(xiàn)的(C++實現(xiàn))

//部分屬性
ObjectMonitor() {
    _count        = 0;     //鎖計數(shù)器
    _owner        = NULL;
    _WaitSet      = NULL;  //處于wait狀態(tài)的線程,會被加入到_WaitSet
    _EntryList    = NULL ; //處于等待鎖block狀態(tài)的線程,會被加入到該列表
  }

本質(zhì)上,監(jiān)視器是一種同步工具,也可以說是一種同步機制,主要特點是:

  • 同步。監(jiān)視器所保護(hù)的臨界區(qū)代碼是互斥地執(zhí)行的。一個監(jiān)視器是一個運行許可,任一線程進(jìn)入臨界區(qū)代碼都需要獲得這個許可,離開時把許可歸還。
  • 協(xié)作。監(jiān)視器提供Signal機制,允許正持有許可的線程暫時放棄許可進(jìn)入阻塞等待狀態(tài),等待其他線程發(fā)送Signal去喚醒;其他擁有許可的線程可以發(fā)送Signal,喚醒正在阻塞等待的線程,讓它可以重新獲得許可并啟動執(zhí)行。

在這里插入圖片描述

每個java 對象都可以關(guān)聯(lián)一個 Monitor 對象,如果使用 synchronized 給對象上鎖(重量級)之后,該對象頭的mark word中就被設(shè)置指向 Monitor 對象的指針。

(1) 如果使用 synchronized 給obj對象上鎖,obj對象的markword就會指向一個monitor鎖對象;

(2) 剛開始 Monitor 中 Owner 為 null ;

(3) 當(dāng)Thread-2線程持有monitor對象后,就會把monitor中的owner變量設(shè)置為當(dāng)前線程Thread-2;

(4) 當(dāng)Thread-3線程想要執(zhí)行臨界區(qū)的代碼時,要判斷monitor對象的屬性O(shè)wner是否為null,如果為null,Thread-3線程就持有了對象鎖,如果不為null,Thread-3線程就會放入monitor的EntryList阻塞隊列中,處于阻塞狀態(tài)Blocked。

(5) 在 Thread-2 上鎖的過程中,如果Thread-4,Thread-5 也來執(zhí)行 synchronized(obj),也會進(jìn)入EntryList BLOCKED ;

(6) Thread-2 執(zhí)行完同步代碼塊的內(nèi)容,就會釋放鎖,將owner變量置為null,并喚醒EntryList 中阻塞的線程來競爭鎖,競爭時是非公平的 ;

(7) 圖中 WaitSet 中的 Thread-0,Thread-1 是之前獲得過鎖,但條件不滿足進(jìn)入 WAITING 狀態(tài)的線程,后面講 wait-notify 時會分析

2. snychronized同步代碼塊原理

public class TestSynchronized {
    static final Object obj = new Object();
    static int i=0;
    public static void main(String[] args) {
        synchronized (obj){
            i++;
        }
    }
}

將上面的代碼反編譯為字節(jié)碼文件:

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2         // 獲取obj對象
         3: dup
         4: astore_1
         5: monitorenter		//將obj對象的markword置為monitor指針
         6: getstatic     #3                  
         9: iconst_1
        10: iadd
        11: putstatic     #3                  
        14: aload_1
        15: monitorexit			//同步代碼塊正常執(zhí)行時,將obj對象的markword重置,喚醒EntryList
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit			//同步代碼塊出現(xiàn)異常時,將obj對象的markword重置,喚醒EntryList
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             6    16    19   any  //監(jiān)測6-16行jvm指令,如果出現(xiàn)異常就會到第19行
            19    22    19   any

這兩條指令的作用,我們直接參考JVM規(guī)范中描述:

monitorenter 指令:

每個對象有一個監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時就會處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時嘗試獲取monitor的所有權(quán),過程如下:

(1) 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者

(2) 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1.

(3) 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)。

monitorexit指令:

執(zhí)行monitorexit的線程必須是持有obj鎖對象的線程

指令執(zhí)行時,monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程釋放monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權(quán)。

Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴于monitor對象,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法,否則會拋出IllegalMonitorStateException的異常的原因。

3. synchronized同步方法原理

public class TestSynchronized {
    static int i=0;
    public synchronized  void add(){
        i++;
    }
}

對應(yīng)的字節(jié)碼指令:

 public synchronized void add();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field i:I
         3: iconst_1
         4: iadd
         5: putstatic     #2                  // Field i:I
         8: return

從反編譯的結(jié)果來看,方法的同步并沒有通過指令monitorentermonitorexit來完成不過相對于普通方法,其常量池中多了ACC_SYNCHRONIZED標(biāo)示符。JVM就是根據(jù)該標(biāo)示符來實現(xiàn)方法的同步的:當(dāng)方法調(diào)用時,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質(zhì)上沒有區(qū)別,只是方法的同步是一種隱式的方式來實現(xiàn),無需通過字節(jié)碼來完成。

4. 重量級鎖的開銷

處于ContentionList、EntryList、WaitSet中的線程都處于阻塞狀態(tài),線程的阻塞或者喚醒都需要操作系統(tǒng)來幫忙,Linux內(nèi)核下采用pthread_mutex_lock系統(tǒng)調(diào)用實現(xiàn),進(jìn)程需要從用戶態(tài)切換到內(nèi)核態(tài)。

用戶態(tài)是應(yīng)用程序運行的空間,為了能訪問到內(nèi)核管理的資源(例如CPU、內(nèi)存、I/O),可以通過內(nèi)核態(tài)所提供的訪問接口實現(xiàn),這些接口就叫系統(tǒng)調(diào)用。

pthread_mutex_lock系統(tǒng)調(diào)用是內(nèi)核態(tài)為用戶態(tài)進(jìn)程提供的Linux內(nèi)核態(tài)下互斥鎖的訪問機制,所以使用pthread_mutex_lock系統(tǒng)調(diào)用時,進(jìn)程需要從用戶態(tài)切換到內(nèi)核態(tài),而這種切換是需要消耗很多時間的,有可能比用戶執(zhí)行代碼的時間還要長。

由于JVM輕量級鎖使用CAS進(jìn)行自旋搶鎖,這些CAS操作都處于用戶態(tài)下,進(jìn)程不存在用戶態(tài)和內(nèi)核態(tài)之間的運行切換,因此JVM輕量級鎖開銷較小。而JVM重量級鎖使用了Linux內(nèi)核態(tài)下的互斥鎖,這是重量級鎖開銷很大的原因。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容! 

相關(guān)文章

最新評論