synchronized 使用 Monitor 实现同步解析

Java 并发编程的艺术第2版学习笔记

原书:《Java 并发编程的艺术第2版》 | 作者:方腾飞 魏鹏 程晓明 | 2023 年 9 月 | 机械工业出版社
1.3.1 volatile 和 synchronized 关键字 | 13 页

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Synchronized {

    public static void main(String[] args) {
        synchronized (Synchronized.class) {
            m();
        }
    }

    public static synchronized void m() {

    }
}

查看字节码 javap -c Synchronized.class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class com.wyyl1.study.chapter1.Synchronized {
  public com.wyyl1.study.chapter1.Synchronized();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #7                  // class com/wyyl1/study/chapter1/Synchronized
       2: dup
       3: astore_1
       4: monitorenter
       5: invokestatic  #9                  // Method m:()V
       8: aload_1
       9: monitorexit
      10: goto          18
      13: astore_2
      14: aload_1
      15: monitorexit
      16: aload_2
      17: athrow
      18: return
    Exception table:
       from    to  target type
           5    10    13   any
          13    16    13   any

  public static synchronized void m();
    Code:
       0: return
}

为什么有 2 个 monitorexit 指令? AI 提供

两个 monitorexit 指令确保了正常、异常路径的锁都能被释放

  • 第一个 monitorexit(第 9 行)是在正常执行路径上退出同步代码块。
  • 第二个 monitorexit(第 15 行)位于 Exception table 中,对应异常处理路径。当在同步代码块内抛出异常时,JVM会确保在异常处理完毕后仍然释放监视器(即执行 monitorexit),以保证同步资源的正确释放。

Monitor

使用 javap -v Synchronized.class 查看 ACC_SYNCHRONIZED

1
2
3
4
5
6
7
8
public static synchronized void m();
    descriptor: ()V
    flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 13: 0

AI:对于同步方法,ACC_SYNCHRONIZED 标志使得 JVM 能够隐式地在方法调用前后执行 monitorentermonitorexit 指令(这些指令不会出现在字节码中,而是由 JVM 自动插入)。 monitorexit 指令确保了即使方法执行过程中抛出异常,监视器也能得到释放,避免了死锁的发生。

在上面的 class 信息中,对于同步块的实现使用了 monitorentermonitorexit 指令。
而同步方法则是依靠方法修饰符上的 ACC_SYNCHRONIZED 完成的。
两种方式的本质都是对一个对象的监视器(monitor)进行获取,获取过程是排他的。
同一时刻只能有一个线程获取到由 synchronized 保护的对象的监视器

任意一个对象都拥有自己的监视器,当这个对象由同步块或者同步方法调用时,执行方法的线程必须先获取该对象的监视器,才能进入同步块或同步方法
没有获取到监视器的线程会被阻塞在同步块和同步块方法的入口处,进入BLOCKED 状态。

javap 命令 AI 提供

javap 是 Java 虚拟机的一个工具,用于反编译 Java 类文件。它能够将 Java 字节码文件转换为 Java 源代码的伪代码表示,从而帮助开发者理解字节码的底层结构。 要使用 javap,你需要首先确保它已经安装在你的系统中。在大多数 Java 开发环境中,javap 通常已经作为 Java 开发工具包(JDK)的一部分安装好了。 以下是 javap 的一些基本用法:

  1. 查看类的字节码信息
    1
    
    javap -verbose ClassName
    
    这个命令会输出指定类(ClassName)的字节码信息,包括常量池、类访问标志、类索引、父类索引、接口索引等。
  2. 查看类的属性
    1
    
    javap -private ClassName
    
    这个命令会输出指定类(ClassName)的私有成员变量和私有方法。
  3. 查看类的字节码指令
    1
    
    javap -c ClassName
    
    这个命令会输出指定类(ClassName)的字节码指令,但不包括常量池和属性信息。
  4. 查看类的源代码
    1
    
    javap -s ClassName
    
    这个命令会输出指定类(ClassName)的源代码的伪代码表示。
  5. 查看类的属性
    1
    
    javap -p ClassName
    
    这个命令会输出指定类(ClassName)的私有成员变量和私有方法。
  6. 查看类的完整信息
    1
    
    javap -v ClassName
    
    这个命令会输出指定类(ClassName)的详细信息,包括字节码指令、常量池、属性等。 例如,如果你想查看名为 MyClass 的类的字节码信息,你可以使用以下命令:
1
javap -v MyClass

请注意,使用 javap 工具反编译的代码只是字节码的伪代码表示,并不是真正的 Java 源代码。因此,反编译出来的代码可能与原始的 Java 源代码存在差异。此外,使用 javap 反编译可能会违反某些软件的许可协议,因此在使用之前应确保你有权反编译该软件。

comments powered by Disqus