java併發--synchronized原理

相信對於各位java大佬,synchronized關鍵字大家可能並不陌生,今天這邊來詳細聊聊這個synchronized關鍵字。

相信有有寫java併發基礎的同學可能知道,synchronized關鍵字是利用鎖的機制來實現代碼同步的。對於java中的鎖機制有如下兩個特性(還是要了解下的,吹牛逼的時候好提升下b格)

互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(複合操作)進行訪問。互斥性我們也往往稱爲操作的原子性。
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。

關於synchronized關鍵字,有如下幾種用法:
根據修飾對象分類
1、同步方法
(1) 同步非靜態方法
Public synchronized void methodName(){
……
}
(2) 同步靜態方法
Public synchronized static void methodName(){
……
}
在這裏插入圖片描述
2、同步代碼塊
synchronized(this|object) {}
synchronized(類.class) {}
Private final Object MUTEX =new Object();
Public void methodName(){
Synchronized(MUTEX ){
……
}
}
在這裏插入圖片描述
關於這兩種方式具體有什麼區別呢?

在說這個之前,我們先來講講對象鎖和類鎖:
(1)獲取對象鎖(修飾非靜態方法):synchronized(this|object) {}
在 Java 中,每個對象都有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,通常被稱爲“內置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨立的對象鎖,互不干擾。
(2)獲取類鎖(修飾靜態方法):synchronized(類.class) {}
在 Java 中,針對每個類也有一個鎖,可以稱爲“類鎖”,類鎖實際上是通過對象鎖實現的,即類的 Class 對象鎖。每個類只有一個 Class 對象,所以每個類只有一個類鎖。

在 Java 中,每個對象都會有一個 monitor 對象,監視器。

  1.     某一線程佔有這個對象的時候,先monitor 的計數器是不是0,如果是0還沒有線程佔有,這個時候線程佔有這個對象,並且對這個對象的monitor+1;如果不爲0,表示這個線程已經被其他線程佔有,這個線程等待。當線程釋放佔有權的時候,monitor-1;
    
  2.     同一線程可以對同一對象進行多次加鎖,+1,+1,重入性,因此synchronized也可以理解爲一把可重入鎖
    

下面我麼便來驗證下上面的,這裏介紹一個java反編譯命令:javap -v

baomw@baomingwudeMacBook-Air:~/IdeaProjects/test1/target/classes/com/baomw$     javap -v SyncTest.class
Classfile /Users/baomw/IdeaProjects/test1/target/classes/com/baomw/SyncTest.class
  Last modified 2019-3-17; size 763 bytes
  MD5 checksum d728833a93ce9cf96516976dd96d5ed7
  Compiled from "SyncTest.java"
public class com.baomw.SyncTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#29         // java/lang/Object."<init>":()V
   #2 = Fieldref           #4.#30         // com/baomw/SyncTest.num:I
   #3 = Methodref          #4.#31         // com/baomw/SyncTest.add:()V
   #4 = Class              #32            // com/baomw/SyncTest
   #5 = Class              #33            // java/lang/Object
   #6 = Utf8               num
   #7 = Utf8               I
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/baomw/SyncTest;
  #15 = Utf8               add
  #16 = Utf8               remove
  #17 = Utf8               StackMapTable
  #18 = Class              #32            // com/baomw/SyncTest
  #19 = Class              #33            // java/lang/Object
  #20 = Class              #34            // java/lang/Throwable
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               MethodParameters
  #26 = Utf8               <clinit>
  #27 = Utf8               SourceFile
  #28 = Utf8               SyncTest.java
  #29 = NameAndType        #8:#9          // "<init>":()V
  #30 = NameAndType        #6:#7          // num:I
  #31 = NameAndType        #15:#9         // add:()V
  #32 = Utf8               com/baomw/SyncTest
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/Throwable
{
  public com.baomw.SyncTest();
    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 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/baomw/SyncTest;

  public static synchronized void add();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field num:I
         3: iconst_1
         4: iadd
         5: putstatic     #2                  // Field num:I
         8: return
      LineNumberTable:
        line 18: 0
        line 19: 8

  public void remove();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field num:I
         7: iconst_1
         8: isub
         9: putstatic     #2                  // Field num:I
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 23: 0
        line 24: 4
        line 25: 12
        line 26: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/baomw/SyncTest;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/baomw/SyncTest, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokestatic  #3                  // Method add:()V
         3: return
      LineNumberTable:
        line 29: 0
        line 30: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #2                  // Field num:I
         4: return
      LineNumberTable:
        line 15: 0
}
SourceFile: "SyncTest.java"

測試代碼發編譯後的指令如上:
在這裏插入圖片描述
如上,在計算機指令中,當synchronized修飾我們的方法時,在方法入口增加ACC_SYNCHRONIZED 對整個方法加鎖
而在修飾我們的代碼塊時,在代碼塊的執行入口和出口處增加Monitorenter和
Monitorexit,注意這兩個總是成對出現的。

當然最後在使用我們的synchronized的時候,以下問題還是需要注意下的:
(1)與moniter關聯的對象不能爲空
這個應該不用多說,因爲我們的每一個java對象都和一個monitor關聯,既然對象都爲空了,monitor也就無從談起了。
(2)synchronized作用域太大
在使用中應該儘可能減少鎖的粒度,不然作用域太大,作用域內的代碼很可能還會出現併發問題,其次,代碼都變串行執行,效率低
(3)不同的monitor企圖鎖相同的方法
(4)多個鎖的交叉導致死鎖

好了關於synchronized關鍵子暫時先講到這裏,後續有相關知識再來補充。

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