探索 Java 同步锁机制 synchronized 的原理(二)

75 阅读2分钟

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

上一篇文章,探索 Java 同步锁机制 synchronized 的原理(一) 我们简单学习和了解了 synchronized 的基本使用和 Java 的内置锁机制,今天我们来通过 Java 反编译的技术来看看 synchronized 的实现原理。

synchronized 修饰普通方法

首先我们看看下列代码:

package com.zhongger.threadLearn;

/**
 * @author zhongmingyi
 * @date 2023/2/15 7:14 下午
 */
public class SynchronizedTest1 {
    public synchronized void method1() {
        System.out.println("method1 start");
        try {
            System.out.println("test synchronized method1");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method1 end");
    }

    public synchronized void method2() {
        System.out.println("method2 start");
        try {
            System.out.println("test synchronized method2");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method2 end");
    }

    public static void main(String[] args) {
        SynchronizedTest1 test1 = new SynchronizedTest1();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test1.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test1.method2();
            }
        }).start();
    }
}

输出的结果如下:

image.png

结果分析:

  • 线程 1 先获取到 SynchronizedTest1 对象锁的锁之后,执行 method1 方法
  • method1 方法会执行 3s,直到执行完成后,释放对象锁
  • 线程 2 获取到对象锁之后,再执行 method2 方法

我们使用 javap -v SynchronizedTest1.class 命令反编译这段代码,反编译结如图:

image.png

我们可以看到,由 synchronized 修饰的实例方法被添加上了 ACC_SYNCHRONIZED 标识,JVM在解析的时候,根据这个标识实现方法同步。

synchronized 修饰代码块

synchronized 修饰代码块的实例如下:

package com.zhongger.threadLearn;

/**
 * @author zhongmingyi
 * @date 2023/2/15 7:40 下午
 */
public class SynchronizedTest2 {
    public void method() {
        synchronized (this) {
            System.out.println("test");
        }
    }
}

我们通过反编译命令来看看这种修饰方式是怎么给对象加锁的。执行命令:

javap -c SynchronizedTest2 

输出结果如下:

image.png

我们可以看到关于对象锁的指令 monitorenter 和 monitorexit

  1. monitorenter:每个对象有一个监视器锁 monitor
  • 当 monitor 被占用时就会处于锁定状态,线程执行monitorenter 指令时,会尝试获取 monitor的所有权
  • 如果 monitor 的进入数为 0 ,则该线程进入 monitor ,然后将进入数设置为 1 ,该线程即为 monitor 的所有者
  • 如果线程已经占有该 monitor ,只是重新进入,则进入 monitor 的进入数加 1
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
  1. monitorexit:执行 monitorexit 的线程必须是 objectref 所对应的 monitor 的所有者。
  • 指令执行时,monitor 的进入数减1,如果减1后进入数为0,那线程退出 monitor ,不再是这个 monitor 的所有者。其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权。