Java併發學習筆記3 synchronized

bilibili-Java併發學習筆記3 synchronized

基於 java 1.8.0

P11_synchronized關鍵字原理詳解

P12_透過字節碼理解synchronized關鍵字

JVM學習筆記13 synchronized

  1. 對當前類實例上鎖
package new_package.thread.p12;
public class SynchronizedTest2 {
    public synchronized void hello() {
        int i = 18;
    }

    public void world() {
        synchronized (this) {
            int i = 19;
        }
    }
}
  1. 對當前class類上鎖
package new_package.thread.p12;

public class SynchronizedTest4 {
    public static synchronized void hello() {
        String sync = "static";
    }
    public void world() {
        synchronized (SynchronizedTest4.class) {
            String sync = "xxx";
        }
    }
}
  1. 對類中變量加鎖
package new_package.thread.p12;

public class SynchronizedTest6 {

    Object obj = new Object();

    public void hello() {
        synchronized (obj) {
            String xxx = "Object";
        }
    }

    public void world() {
        synchronized (obj) {
            String str2 = "world";
            throw  new RuntimeException("my exception");
        }
    }
}


  1. javap -v new_package.thread.p12.SynchronizedTest2
{
  public synchronized void hello();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=2, args_size=1
         0: bipush        18
         2: istore_1
         3: return
      LineNumberTable:
        line 6: 0
        line 7: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lnew_package/thread/p12/SynchronizedTest2;
            3       1     1     i   I

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: bipush        19
         6: istore_2
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_3
        13: aload_1
        14: monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             4     9    12   any
            12    15    12   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 7
        line 13: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  this   Lnew_package/thread/p12/SynchronizedTest2;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class new_package/thread/p12/SynchronizedTest2, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

解析1 :

  • 對於 synchronized 關鍵字修飾方法來說,並沒有 monitorenter 和 monitorexit 指令,而是出現了一個 ACC_SYNCHRONIZED 標識
  • JVM 使用 ACC_SYNCHRONIZED 標識來區分一個方法是否爲同步方法;如果是,執行線程會先持有方法所在對象的 Monitor 對象,然後再去執行方法體,線程執行完後(正常執行完返回,發生異常退出方法)釋放 Monitor 對象
  • 線程持有 Monitor 對象期間,其他線程將無法再獲取當前對象的 Monitor 對象
  1. javap -v new_package.thread.p12.SynchronizedTest4
{
  public new_package.thread.p12.SynchronizedTest4();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lnew_package/thread/p12/SynchronizedTest4;

  public static synchronized void hello();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=1, args_size=0
         0: ldc           #2                  // String static
         2: astore_0
         3: return
      LineNumberTable:
        line 5: 0
        line 6: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3       1     0  sync   Ljava/lang/String;

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #3                  // class new_package/thread/p12/SynchronizedTest4
         2: dup
         3: astore_1
         4: monitorenter
         5: ldc           #4                  // String xxx
         7: astore_2
         8: aload_1
         9: monitorexit
        10: goto          18
        13: astore_3
        14: aload_1
        15: monitorexit
        16: aload_3
        17: athrow
        18: return
      Exception table:
         from    to  target type
             5    10    13   any
            13    16    13   any
      LineNumberTable:
        line 9: 0
        line 10: 5
        line 11: 8
        line 12: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  this   Lnew_package/thread/p12/SynchronizedTest4;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class new_package/thread/p12/SynchronizedTest4, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

解析 2 :

  • 靜態同步方法,同時使用 ACC_STATIC, ACC_SYNCHRONIZED 兩個標識
  • 線程執行時,首先獲取當前 Class 類對象(注意區別於實例對象)的 Monitor 對象,…
  1. javap -v new_package.thread.p12.SynchronizedTest6
{
  java.lang.Object obj;
    descriptor: Ljava/lang/Object;
    flags:

  public new_package.thread.p12.SynchronizedTest6();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 3: 0
        line 5: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lnew_package/thread/p12/SynchronizedTest6;

  public void hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: getfield      #3                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: ldc           #4                  // String Object
         9: astore_2
        10: aload_1
        11: monitorexit
        12: goto          20
        15: astore_3
        16: aload_1
        17: monitorexit
        18: aload_3
        19: athrow
        20: return
      Exception table:
         from    to  target type
             7    12    15   any
            15    18    15   any
      LineNumberTable:
        line 8: 0
        line 9: 7
        line 10: 10
        line 11: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  this   Lnew_package/thread/p12/SynchronizedTest6;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 15
          locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=1
         0: aload_0
         1: getfield      #3                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: ldc           #5                  // String world
         9: astore_2
        10: new           #6                  // class java/lang/RuntimeException
        13: dup
        14: ldc           #7                  // String my exception
        16: invokespecial #8                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        19: athrow
        20: astore_3
        21: aload_1
        22: monitorexit
        23: aload_3
        24: athrow
      Exception table:
         from    to  target type
             7    23    20   any
      LineNumberTable:
        line 14: 0
        line 15: 7
        line 16: 10
        line 17: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      10     2  str2   Ljava/lang/String;
            0      25     0  this   Lnew_package/thread/p12/SynchronizedTest6;
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
}

解析3 :

  • synchronized 代碼塊,字節碼層次上是通過 monitorenter 和 monitorexit 指令來實現獲取鎖釋放鎖
  • 當線程進入到 monitorenter 指令後,線程將會持有 Monitor 對象,退出 monitorenter 指令後,線程將會釋放 Monitor 對象;
  • hello 方法有兩種結束方式:正常執行,異常結束,所以退出路徑有兩個,有兩個 monitorexit 指令
  • world 方法主動拋出異常,所以沒有正常執行結束流程,只有一個 monitorexit 指令

P14_自旋對於synchronized關鍵字的底層意義與價值分析

  • JVM 的同步是基於進入與退出監視器對象(管程對象 Monitor)來實現的,每個對象實例都會有一個 Monitor 對象,Monitor 對象會和 java 對象一同創建和銷燬,Monitor 對象是由 C++ 來實現的。
  • 當多個線程同時訪問同一段同步代碼時,這些線程會被放到一個 EntryList 集合中(處於阻塞狀態的線程也會在該列表中);當某一線程獲取到對象的 Monitor 對象時,Monitor 依賴於底層操作系統的 mutex lock 來實現互斥,線程獲取 mutex 成功,則會持有該 mutex ,這時其他線程就無法再獲取到該 mutex 。
  • 如果線程調用了 wait 方法,那麼該線程就會釋放所持有的 mutex,並且該線程會進入到 WaieSet 集合(等待集合)中,等待下一次被其他線程調用 notify/notifyAll 喚醒。如果當前線程執行返回,也會釋放所持有的 mutex 。
  • 同步鎖在這種實現方式當中,因爲 Monitor 是依賴於底層的操作系統實現,這樣就存在用戶態內核態之間的切換,所以會增加性能開銷
  • 通過對象互斥鎖的概念來保證共享數據操作的完整性。每個對象都對應一個可稱爲互斥鎖的標記,這個標記用於保證在任何時刻,只能有一個線程訪問該對象。
  • 那些處於 EntryList 和 WaitSet 中的線程均處於阻塞狀態,阻塞操作是由操作系統完成的,在 Linux 系統下是通過 pthread_mutex_lock 函數實現的。線程被阻塞後便會進入到內核調度狀態,這會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能。
  • 解決上述問題的辦法便是自旋。其原理是:當發生對 Monitor 的爭搶時,若 Owner(持有 Monitor 的線程) 能夠在很短時間內釋放掉鎖,則那些正在爭搶鎖的線程就可以稍微等待一下(即所謂的自旋),在 Owner 線程釋放鎖之後,自旋的線程可能會立即獲取到鎖,從而避免阻塞。不過,當 Owner 線程運行時間超過臨界值後,自旋線程自旋一段時間依然無法獲取到鎖,這時自旋線程則會停止自旋而進入到阻塞狀態。所以總體思想是:先自旋,不成功再進行阻塞,儘量降低阻塞的可能性,這對那些執行時間很短的代碼塊來說有極大的性能提升。顯然,自旋(需要消耗 CPU 性能)在多處理器(多核心)上纔有意義。

P15_互斥鎖屬性詳解與Monitor對象特性解說

互斥鎖的屬性:

  1. PTHREAD_MUTEX_TIMED_NP

屬性缺省值普通鎖 : 當一個線程加鎖之後,其餘請求鎖的線程將會形成一個等待隊列,並且在解鎖後按照優先級獲取到鎖,這種策略可以確保資源分配的公平性。

  1. PTHREAD_MUTEX_RECURSIVE_NP

嵌套鎖 : 允許一個線程對同一個鎖成功獲取多次,並通過 unlock 解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新進行競爭。

  1. PTHREAD_MUTEX_ERRORCHECK_NP

檢錯鎖 : 如果一個線程請求同一個鎖,則返回 EDEADLK ,否則與 PTHREAD_MUTEX_TIMED_NP 類型動作相同,這樣就保證了當不允許多次加鎖時不會出現最簡單情況下的死鎖。

  1. PTHREAD_MUTEX_ADAPTIVE_NP

適應鎖 : 動作最簡單的鎖,僅僅等待解鎖後重新競爭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章