Java中的各種鎖(2)——隱式鎖synchronized

2 Java中的隱式鎖

在Java中,提供了關(guān)鍵字synchronized。這個(gè)關(guān)鍵字可以應(yīng)用在不同的地方,如下面的表格所示:

應(yīng)用位置 鎖存在于哪里 代碼示例
實(shí)例方法 當(dāng)前類(lèi)的實(shí)例對(duì)象 public synchronized void method(){......}
靜態(tài)方法 當(dāng)前類(lèi)對(duì)象 public static synchronized void method(){......}
代碼塊 指定的實(shí)例對(duì)象 synchronized(object){......} //這里的object可以是任何的對(duì)象實(shí)例,比如this,Integer.MAX_VALUE這樣的對(duì)象實(shí)例,都可以
代碼塊 指定的類(lèi)對(duì)象 syncrhonized(Demo.class){......}

使用了synchronized關(guān)鍵字之后,就可以給相應(yīng)的方法/代碼塊加上鎖,保證在一個(gè)JVM中,同時(shí)只有一個(gè)線程能夠執(zhí)行這個(gè)方法/這個(gè)代碼。但是我們并沒(méi)有看到加鎖和釋放鎖的操作,因此又被稱為“隱式鎖”。

synchronized的這個(gè)功能是在JVM中通過(guò)使用monitor來(lái)實(shí)現(xiàn)的。

2.1 sychronized對(duì)應(yīng)的字節(jié)碼

我們先來(lái)看看下面的代碼:

public class DemoOnInstance {
    public int value = 0;

  public int addAndGet(int increment){
      synchronized (this){
          value = value + increment;
          return value;
      }
  }
}

在上面的代碼中的addAndGet方法里面,我們使用了synchronized關(guān)鍵字標(biāo)記了一個(gè)同步代碼塊,并且是將鎖加在了當(dāng)前實(shí)例this上。那么,JVM是怎么幫我們實(shí)現(xiàn)鎖的呢。我們可以看看生成的字節(jié)碼。
我們可以在編譯后的class文件上使用javap命令來(lái)查看字節(jié)碼,javap -v DemoOnInstance.class。得到的結(jié)果會(huì)包含下面的片段,這個(gè)片段是addAndGet方法對(duì)應(yīng)的字節(jié)碼。

  public int addAndGet(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=2
         0: aload_0
         1: dup
         2: astore_2
         3: monitorenter
         4: aload_0
         5: aload_0
         6: getfield      #2                  // Field value:I
         9: iload_1
        10: iadd
        11: putfield      #2                  // Field value:I
        14: aload_0
        15: getfield      #2                  // Field value:I
        18: aload_2
        19: monitorexit
        20: ireturn
        21: astore_3
        22: aload_2
        23: monitorexit
        24: aload_3
        25: athrow
      Exception table:
         from    to  target type
             4    20    21   any
            21    24    21   any

上面的字節(jié)碼中,請(qǐng)注意標(biāo)記行號(hào)為3和19的兩行,分別為monitorentermonitorexit。這兩行指令告訴JVM要獲取Monitor和釋放Monitor。 我們可以看到第23行也有monitorexit,這個(gè)是異常情況下的釋放monitor的處理。因此無(wú)論代碼是正常執(zhí)行還是異常執(zhí)行,都會(huì)執(zhí)行monitorexit,保證鎖會(huì)被釋放。

當(dāng)synchronized關(guān)鍵字應(yīng)用在方法上的時(shí)候,情況略有不同。

public class DemoOnInstanceMethod {
  private int value = 0;

  public synchronized int addAndGet(int increment){
    value = value + increment;
    return value;
  }
}

上面的代碼中的addAndGet方法編譯后的字節(jié)碼如下:

  public synchronized int addAndGet(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=2, args_size=2
         0: aload_0
         1: aload_0
         2: getfield      #2                  // Field value:I
         5: iload_1
         6: iadd
         7: putfield      #2                  // Field value:I
        10: aload_0
        11: getfield      #2                  // Field value:I
        14: ireturn

在字節(jié)碼中,沒(méi)有看到monitor相關(guān)的指令,但是在方法的flags中,有ACC_SYNCHRONIZED這個(gè)值。JVM在執(zhí)行該方法的時(shí)候,如果有這個(gè)flag,就會(huì)獲取鎖,方法退出時(shí)(無(wú)論是正常退出還是異常退出)釋放鎖。

2.2 有了字節(jié)碼之后呢?

synchronize關(guān)鍵字在不同的位置會(huì)生成不同的字節(jié)碼,JVM在執(zhí)行時(shí)會(huì)添加獲取monitor和釋放monitor的操作。 前面我們提到了,不同的代碼鎖定的對(duì)象是不一樣的。那么這個(gè)是怎么實(shí)現(xiàn)的呢?

前面也提到了,不同的代碼方式,鎖存在的位置是不一樣的,這是因?yàn)樵贘ava中,對(duì)應(yīng) synchronized 有兩種鎖:對(duì)象鎖和類(lèi)鎖。
對(duì)象鎖:在非靜態(tài)方法上使用 synchronized 關(guān)鍵字,或者使用 synchronized(objectInstance)這樣的方式來(lái)創(chuàng)建同步代碼塊,使用的是對(duì)象鎖。每個(gè)對(duì)象實(shí)例都有一個(gè)對(duì)象鎖,不同的對(duì)象實(shí)例各自有各自的對(duì)象鎖。對(duì)象鎖是線程可重入的,因此在同一個(gè)線程中,可以在一個(gè)同步方法中調(diào)用另外一個(gè)同步方法。但是,一個(gè)線程在執(zhí)行一個(gè)實(shí)例上的同步代碼,其他線程如果要執(zhí)行同樣實(shí)例上的同步代碼,無(wú)論是不是相同的代碼段,都會(huì)被阻塞。如果兩個(gè)線程執(zhí)行的是同一個(gè)類(lèi)的不同實(shí)例對(duì)象的同步代碼塊,則可以同時(shí)執(zhí)行。

類(lèi)鎖:在靜態(tài)方法上使用 synchronized 關(guān)鍵字,或者使用 synchronized(Demo.class)這樣的方法創(chuàng)建的同步代碼塊,使用的就是類(lèi)鎖。一個(gè)類(lèi)只有一個(gè)類(lèi)鎖(感覺(jué)類(lèi)似靜態(tài)變量)。同樣的,使用同一個(gè)類(lèi)鎖的同步代碼段,同一時(shí)間只有一個(gè)能執(zhí)行。

類(lèi)鎖和對(duì)象鎖互不干擾。

2.3 鎖的升級(jí)

新創(chuàng)建的類(lèi)鎖或者對(duì)象鎖,都是處于偏向鎖的狀態(tài)。在使用過(guò)程中,隨著競(jìng)爭(zhēng)的情況,會(huì)逐步升級(jí)為輕量鎖狀態(tài)或者重量鎖狀態(tài)。這一點(diǎn)在上一篇文章中已經(jīng)講過(guò)了,就不再贅述。

2.4 小結(jié)

synchronized 關(guān)鍵字會(huì)使用“隱式鎖”,這個(gè)是由字節(jié)碼和JVM共同來(lái)實(shí)現(xiàn)的,因此這個(gè)鎖是“本地鎖”,只能應(yīng)用于同一個(gè)JVM中的不同進(jìn)程,不能應(yīng)用于不同的JVM之間。

不同的 synchronized 的用法使用的鎖不盡相同,有對(duì)象鎖和類(lèi)鎖兩種。對(duì)象鎖和類(lèi)鎖都是可重入鎖和獨(dú)享鎖,同時(shí)也都是非公平鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容