1 示例-简单同步代码块

public class SychTest9 {

    public static void main(String[] args) {
        Object o = new Object();
        synchronized (o){
            System.out.println("aaa");
        }
    }
}

使用javap查看指令(复制了部分)

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String aaa
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
        21: monitorexit
        22: goto          30
        25: astore_3
        26: aload_2
        27: monitorexit
        28: aload_3
        29: athrow
        30: return

可以看到下面三个指令,一个monitorenter 指令,两个monitorexit

11: monitorenter   进入管程

21: monitorexit  出管程

27: monitorexit  出管程

  synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位置。**

  当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权

 

  在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对象中都内置了一个 ObjectMonitor对象。

  另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

  在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

  在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止

  这里为什么有两个monitorexit指令呢?

  第一个monitorexit指令是同步代码块正常释放锁的一个标志;

  如果同步代码块中出现Exception或者Error,则会调用第二个monitorexit指令来保证释放锁

 

2 示例-简单同步代码块+异常

在同步代码块中手动抛出异常

public class SychTest10 {

    public static void main(String[] args) throws Exception {
        Object o = new Object();
        synchronized (o){
            System.out.println("aaa");
            throw new Exception();
        }
    }
}

使用javap查看指令(复制了部分)

 /*
  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String aaa
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: new           #6                  // class java/lang/Exception
        23: dup
        24: invokespecial #7                  // Method java/lang/Exception."<init>":()V
        27: athrow
        28: astore_3
        29: aload_2
        30: monitorexit
        31: aload_3
        32: athrow
     */

可以看到monitorentermonitorexitathrow和athrow,保证释放锁

11: monitorenter

27: athrow

30: monitorexit

32: athrow

 

3 示例-普通同步方法

public class SychTest11 {

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

    public static void main(String[] args) {

    }

使用javap查看指令(复制了部分)

 public synchronized void aa();
    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 aaa
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

可以看到,可以没有了monitorenter和monitorexit指令,但是有一个标识ACC_SYNCHRONIZED,标识它是一个同步方法

flags: ACC_PUBLIC, ACC_SYNCHRONIZED

4 示例 静态同步方法

public class SychTest12 {

    public static synchronized  void aa()  {
        System.out.println("aaa");
    }

    public static void main(String[] args) {
        aa();
    }
}

使用javap查看指令(复制了部分)

 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           #3                  // String aaa
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
可以看到,可以没有了monitorenter和monitorexit指令,但是有一个标识ACC_SYNCHRONIZED,标识它是一个同步方法,且ACC_STATIC标识是静态方法
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED

 

5 小结

  synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。正常情况下回有两个monitorexit指令,为了在出现异常也能够成功释放锁。

  synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

  不过两者的本质都是对监视器 monitor 的获取