java synchronize - 線程同步機制

Java支持同步機制的是Monitor。Monitor就像是擁有一個特殊房間的建築,在同一時間裏,這間特殊的房間只能被一個線程擁有。html

  • enter the monitor:進入這幢建築
  • acquiring the monitor:進入建築裏的特殊房間
  • owning the monitor:擁有特殊房間的全部權
  • releasing the monitor:離開特殊的房間
  • exiting the monitor:離開這幢建築

Monitor支持兩種同步機制:java

  • 互斥:經過對象鎖,使得多線程處理能互相獨立的處理共享數據,而不會發生線程不安全
  • 協做:經過對象的wait和notify方法實現,好比一個讀的線程從緩衝區讀數據,另外一個線程負責往緩衝區寫數據,若是緩衝區沒有數據,則讀線程阻塞,有數據時,讀線程就要開始消費

wait-notify又能夠稱做’Singal-continue’。當線程得到 notify,這就是一個信號,線程開始擁有 monitor的全部權,可以 繼續 執行 monitor region。執行完以後,此線程釋放monitor,一個等待的線程則會得到同樣的機會安全

Monitor的模型以下:bash

1 表示線程剛到達 monitor region ,即 enter the monitor
2 表示線程獲取 monitor的全部權,即acquiring the monitor
3 表示線程執行了 wait,交出全部權,即releasing the monitor
4 表示原來擁有 monitor 的線程執行了 notify ,剛好被這個線程獲取全部權
5 表示線程執行完了 monitor region,即exiting the monitor多線程

Monitor特地把等待的線程分紅了兩個部分,Entry Set和Wait Set,Entry Set表示線程剛執行到 Monitor region,而Wait Set則是因爲線程執行了wait方法而進入的區域。注意到Object的 notify 以及 notifyAll 要喚醒的對象就處於 Wait Set,換句話說,若是退出 monitor 的線程沒有執行 notify/notifyAll ,那麼只有 Entry Set 可以獲取執行的權限 。若是執行了,則Entry Set和Wait Set中全部的線程都會競爭誰最終可以獲取 monitor 的能力jvm

一個線程要離開Wait Set,要麼是原擁有 monitor 的線程執行了 notify/notifyAll,要麼是wait的時間到了,JVM 會觸發一個notifyide

對象鎖

Java可以共享的數據包括兩部分:ui

  • 實例對象:存儲在堆中
  • 類實例:存儲在方法區,當鎖一個類的時候,實際上就是鎖類的 Class 對象 對於局部變量,他們存儲在棧中,屬於線程私有,不會存在共享一說。
    單個線程能夠同時鎖住一個對象屢次,JVM會記住鎖住的總次數,每一次釋放鎖,總次數減一,只有在這個次數變成0的時候,這個鎖纔有可能被其它線程持有

monitor region 標識的方式

  • 同步代碼塊
  • 同步方法 JVM使用的指令爲
  • monitorenter 獲取引用對象的鎖
  • monitorexit 是否在monitorenter處得到的對象鎖

同步代碼塊

public class SynchronizedTest {
    private  int i=0;
    public void syn(){
        synchronized (this){
            i++;
        }
    }
}

複製代碼

javap -c SynchronizedTest.class 執行後對應的指令以下this

public class main.lockTest.SynchronizedTest {
  public main.lockTest.SynchronizedTest();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2 // Field i:I
       9: return

  public void syn();
    Code:
       0: aload_0    // aload_0 屬於 aload_<n> 系列指令的一種。表示獲取一個本地變量的引用,而後放入棧中
       1: dup        //彈出棧頂的單字節,而後入棧兩次,至關於拷貝了棧頂元素
       2: astore_1        // astore_<n>系列指令的一種。從棧頂獲取對象的引用,並存入本地變量
       3: monitorenter      //獲取引用對象的鎖
       4: aload_0
       5: dup
       6: getfield      #2 // Field i:I 從棧中獲取對象的引用,而後獲得它的值
       9: iconst_1        // iconst_<n> 的一種,將常量放入棧中
      10: iadd            // 從操做棧中彈出兩個integer,把他們相加,而後將結果從新存入棧中
      11: putfield      #2 // Field i:I 將值存入引用對象
      14: aload_1
      15: monitorexit    // 釋放引用對象的鎖
      16: goto          24 // 跳轉到下一個指令的位置
      19: astore_2
      20: aload_1
      21: monitorexit
      22: aload_2
      23: athrow    //從操做棧中刪掉對象的引用,並拋出這個對象的異常
      24: return     //執行返回
    Exception table:
       from    to  target type
           4    16    19   any
          19    22    19   any
}
複製代碼

注意到,若是拋出了異常,也會執行 monitorexit 。印證了不管如何,只要離開了monitor region,鎖都會被釋放spa

同步方法

public class SynchronizedTest {
    private  int i=0;
    public synchronized void syn(int i){
            i++;
    }
}

複製代碼

對應指令以下

public class main.lockTest.SynchronizedTest {
  public main.lockTest.SynchronizedTest();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2 // Field i:I
       9: return

  public synchronized void syn(int);
    Code:
       0: iinc          1, 1
       3: return
}
複製代碼

能夠看到兩個區別

  1. 在方法上使用 synchronized 沒有用到 monitorenter / monitorexit 指令。這是由於在方法上使用synchronized並不須要一個本地變量槽(slot)來存儲鎖對象
  2. 方法上使用沒有建立一個 異常表

synchronize原理
java 虛擬機指令

相關文章
相關標籤/搜索