管程

管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。

对象的状态

如果一个对象里面有可以被修改的成员变量,那么就称这个对象为有状态的对象。反之就叫做无状态的对象。

临界区


二、Synchornized - 图2

Synchornized 语言的层面实现锁

  1. 修饰实例方法, 锁是当前的对象
  2. 修饰静态方法,锁是当前的类对象
  3. 修饰代码块, 锁是手动指定的对象(任何一个Object都行)
  • 如果一个对象有若干个Synchornized方法,在某一个时刻,这些方法中只能有某一个Synchornized方法被某一个线程所访问,其他的所有线程访问该对象的Synchornized方法都会被等待。因为当前的对象只有一把锁。
  • static 与 synchornized 同时存在时,该锁不再是当前对象的锁,而是当前对象所对应的类的class对象的锁。 :::info 提问:两个线程能同时执行同一个对象的下面两个方法吗?
    image.png
    答:是可以的,因为这两个方法的锁是不同的,method1 的锁是当前Test 对象,method2的锁是这个Test类锁对应的class对象。 :::

    synchronized字节码

    monitorentermonitorexit指令

    image.png
    image.png
    当我们使用synchronized关键字来修饰代码块时,字节码层面上是通过monitorentermonitorexit指令来实现的锁的获取与释放动作。当线程进入到monitorenter指令后,线程将会持有Monitor对象,退出monitorenter指令后,线程将会释放Monitor对象。monitorenter与**monitorexit可能是一对多的关系。
    monitorenter
    指令插入到同步代码块开始的位置,monitorexit **指令插入到同步代码块结束位置,jvm 需要保证每个monitorenter 都有一个 monitorexit 对应。这两个指令隐式的执行了 Lock 和 UnLock 操作
    这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由 synchronized 所保护对象的监视器。
    线程执行到 monitorenter 指令时,会尝试获取对象所对应的 monitor 所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放 monitor 的所有权。

    为什么会有两个monitorexit ?

    为了应对异常情况的发生而产生的一条字节码指令。

    synchronized代码块主动抛出异常的字节码

    1. public void method() {
    2. synchronized (object) {
    3. System.out.println("hello world");
    4. throw new RuntimeException();//主动抛出异常
    5. }
    6. }
    主动抛出异常,这时候javap 会发现只有一个monitorexit。因为这段代码只会有一个异常出口。
    image.png

    synchronized修饰实例方法的字节码

  1. public synchronized void method() {
  2. System.out.println("hello world");
  3. }

发现方法会多一个ACC_SYNCHRONIZED的标志位。image.png

对于synchronized关键字修饰方法来说,并没有出现monitorenter与monitorexit指令**而是出现了一个ACC_SYNCHRONIZED。JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法**;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZED标志,如果有,那么执行线程将会先持有方法所在对象的Monitor对象,然后再去执行方法体;在该方法执行期间,其他任何线程均无法再获取到这个Monitor对象,当线程执行完该方法后,它会释放掉这个Monitor对象。

synchronized修饰静态方法的字节码

image.png

image.png
args_size=0,表示并没有传入this参数