并发编程(十)Synchronize使用方式

163 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

1.Sync初步解读

synchronize作用范围在三个地方是:普通方法;静态方法;同步代码块。

1.普通方法- 锁对象:我们的对象(new 出来的,谁调用这个方法,锁作用于谁身上)

2.静态方法- 锁对象:我们的对象所属的class,全局只有一个。(类型,放到方法区,包括我们的真正的.class文件的二进制文件都最终加载到了运行时数据区的方法区

3.静态代码块- 锁对象:就是我们synchronized关键字后括号里的内容

下面列举一个例子进行深入说明:

public class SyncUsingWay {
    // 普通方法- 锁对象:我们的对象(new 出来的,谁调用这个方法,锁作用于谁身上)
    public synchronized void SyncMethod() {
        System.out.println("SyncMethod");
    }
    // 静态方法- 锁对象:我们的对象所属的class,全局只有一个。(类型,放到方法区的
    // 包括我们的真正的.class文件的二进制文件都最终加载到了运行时数据区的方法区)
    public synchronized static void StaticSyncMethod(){
        System.out.println("StaticSyncMethod");
    }

    public void method(){
        // 静态代码块
        synchronized (this) {
            System.out.println("method");
        }
    }
}

2.Sync深入解读

通过 javap -v 深入查看:

image.png

把所有打印摘出如下:

Classfile /G:/test/algorithm/target/classes/com/readBook/juc/SyncUsingWay.class
  Last modified 2022-11-25; size 777 bytes     
  MD5 checksum 5da1ce76ceb1bf7b7ab043c9bac0ea82
  Compiled from "SyncUsingWay.java"            
public class com.readBook.juc.SyncUsingWay                                                    
  minor version: 0                                                                            
  major version: 52                                                                           
  flags: ACC_PUBLIC, ACC_SUPER                                                                
Constant pool:                                                                                
   #1 = Methodref          #8.#25         // java/lang/Object."<init>":()V                    
   #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;       
   #3 = String             #16            // SyncMethod                                       
   #4 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #17            // StaticSyncMethod
   #6 = String             #18            // method
   #7 = Class              #30            // com/readBook/juc/SyncUsingWay
   #8 = Class              #31            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/readBook/juc/SyncUsingWay;
  #16 = Utf8               SyncMethod
  #17 = Utf8               StaticSyncMethod
  #18 = Utf8               method
  #19 = Utf8               StackMapTable
  #20 = Class              #30            // com/readBook/juc/SyncUsingWay
  #21 = Class              #31            // java/lang/Object
  #22 = Class              #32            // java/lang/Throwable
  #23 = Utf8               SourceFile
  #24 = Utf8               SyncUsingWay.java
  #25 = NameAndType        #9:#10         // "<init>":()V
  #26 = Class              #33            // java/lang/System
  #27 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #28 = Class              #36            // java/io/PrintStream
  #29 = NameAndType        #37:#38        // println:(Ljava/lang/String;)V
  #30 = Utf8               com/readBook/juc/SyncUsingWay
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/Throwable
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (Ljava/lang/String;)V
{
  public com.readBook.juc.SyncUsingWay();
    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   Lcom/readBook/juc/SyncUsingWay;

  public synchronized void SyncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String SyncMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/readBook/juc/SyncUsingWay;

  public static synchronized void StaticSyncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String StaticSyncMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8

  public void method();
    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 java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #6                  // String method
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        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 16: 0
        line 17: 4
        line 18: 12
        line 19: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/readBook/juc/SyncUsingWay;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/readBook/juc/SyncUsingWay, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "SyncUsingWay.java"

这里我们摘出来三个方法的反编译来分析:

public synchronized void SyncMethod() {
    System.out.println("SyncMethod");
}

这个方法反编译对应着下面:

  public synchronized void SyncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String SyncMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/readBook/juc/SyncUsingWay;

我们可以看大flags: ACC_PUBLIC, ACC_SYNCHRONIZED,这个里面的ACC_SYNCHRONIZED就是加锁的方式,所以在普通方法加关键字synchronize的时候,会在flags里加入ACC_SYNCHRONIZED进行标志。

public synchronized static void StaticSyncMethod(){
    System.out.println("StaticSyncMethod");
}
这个方法反编译对应着下面:
  public static synchronized void StaticSyncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String StaticSyncMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8

同理我们可以看到这个静态方法加关键字synchronize的时候,也会在flags里加入ACC_SYNCHRONIZED进行标志。这里还会比上面多一个ACC_STATIC,因为这个是静态方法的标志。

public void method(){
    // 静态代码块
    synchronized (this) {
        System.out.println("method");
    }
}
这个方法反编译对应着下面:
 public void method();
    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 java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #6                  // String method
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit   // 正常退出同步代码块
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit  //防止任何异常情况下,退出同步代码块。JVM仍然可以释放锁
        20: aload_2
        line 19: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/readBook/juc/SyncUsingWay;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/readBook/juc/SyncUsingWay, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

我们可以看到这个是静态代码块的方式加锁,但是里面flags里并没有ACC_SYNCHRONIZED,那这个是怎么回事呢?

我们可以看到:

3: monitorenter表示进入同步代码块()

13: monitorexit表示正常退出同步代码块

19: monitorexit表示防止任何异常情况下,退出同步代码块。JVM仍然可以释放锁

所以同步代码块和在方法中的不同。