被问到傻傻不懂synchronized底层原理

2020-06-15 16:56:14 浏览数 (1)

0x01:synchronized的基本语法

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

0x02:代码分析synchronized

代码语言:javascript复制
package com.lesson8;

public class SynchronizedDemo {

    public Object lock = new Object();

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

    public static synchronized void  syncStaticMethod(){
        System.out.println("==syncStaticMethod==");
    }

    public void  syncBlockCode(){
        synchronized (lock) {
            System.out.println("==syncBlockCode==");
        }
    }

}

代码中包三个方法,分别是synchronized修饰实例方法、synchronized静态方法synchronized修饰代码块。使用命令:

javap -v SynchronizedDemo.class

得到如下汇编指令:

代码语言:javascript复制
Classfile /D:/jmeterws/xml/com-lesson8/target/classes/com/lesson8/SynchronizedDemo.class
  Last modified 2020-4-5; size 926 bytes
  MD5 checksum f23cff99c70b8a401db1cc3bd74538ec
  Compiled from "SynchronizedDemo.java"
public class com.lesson8.SynchronizedDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/lesson8/SynchronizedDemo
   #2 = Utf8               com/lesson8/SynchronizedDemo
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               lock
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         // java/lang/Object."<init>":()V
  #11 = NameAndType        #7:#8          // "<init>":()V
  #12 = Fieldref           #1.#13         // com/lesson8/SynchronizedDemo.lock:Ljava/lang/Object;
  #13 = NameAndType        #5:#6          // lock:Ljava/lang/Object;
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/lesson8/SynchronizedDemo;
  #18 = Utf8               syncCommonMethod
  #19 = Fieldref           #20.#22        // java/lang/System.out:Ljava/io/PrintStream;
  #20 = Class              #21            // java/lang/System
  #21 = Utf8               java/lang/System
  #22 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = String             #26            // ==syncCommonMethod==
  #26 = Utf8               ==syncCommonMethod==
  #27 = Methodref          #28.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #28 = Class              #29            // java/io/PrintStream
  #29 = Utf8               java/io/PrintStream
  #30 = NameAndType        #31:#32        // println:(Ljava/lang/String;)V
  #31 = Utf8               println
  #32 = Utf8               (Ljava/lang/String;)V
  #33 = Utf8               syncStaticMethod
  #34 = String             #35            // ==syncStaticMethod==
  #35 = Utf8               ==syncStaticMethod==
  #36 = Utf8               syncBlockCode
  #37 = String             #38            // ==syncBlockCode==
  #38 = Utf8               ==syncBlockCode==
  #39 = Utf8               StackMapTable
  #40 = Class              #41            // java/lang/Throwable
  #41 = Utf8               java/lang/Throwable
  #42 = Utf8               SourceFile
  #43 = Utf8               SynchronizedDemo.java
{
  public java.lang.Object lock;
    descriptor: Ljava/lang/Object;
    flags: ACC_PUBLIC

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

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

  public static synchronized void syncStaticMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #34                 // String ==syncStaticMethod==
         5: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 12: 0
        line 13: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public void syncBlockCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: getfield      #12                 // Field lock:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #37                 // String ==syncBlockCode==
        12: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: aload_1
        16: monitorexit
        17: goto          23
        20: aload_1
        21: monitorexit
        22: athrow
        23: return
      Exception table:
         from    to  target type
             7    17    20   any
            20    22    20   any
      LineNumberTable:
        line 16: 0
        line 17: 7
        line 16: 15
        line 19: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  this   Lcom/lesson8/SynchronizedDemo;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class com/lesson8/SynchronizedDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "SynchronizedDemo.java"

分析如上代码发现synchronized修饰实例方法、synchronized静态方法synchronized修饰代码块不一样,synchronized修饰实例方法和synchronized静态方法一样

synchronized修饰代码块

涉及两条指令:

  • monitorenter:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。

如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

  • monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor 的所有权。

synchronized修饰实例方法

synchronized静态方法

从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现)。相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。

JVM就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

0 人点赞