百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

如何看到 synchronized 背后的“monitor 锁”?

wptr33 2025-03-24 00:22 8 浏览

本文我们研究下 synchronized 背后的 monitor 锁。

获取和释放 monitor 锁的时机

我们都知道,最简单的同步方式就是利用 synchronized 关键字来修饰代码块或者修饰一个方法,那么这部分被保护的代码,在同一时刻就最多只有一个线程可以运行,而 synchronized 的背后正是利用 monitor 锁实现的。所以首先我们来看下获取和释放 monitor 锁的时机,每个 Java 对象都可以用作一个实现同步的锁,这个锁也被称为内置锁或 monitor 锁,获得 monitor 锁的唯一途径就是进入由这个锁保护的同步代码块或同步方法,线程在进入被 synchronized 保护的代码块之前,会自动获取锁,并且无论是正常路径退出,还是通过抛出异常退出,在退出的时候都会自动释放锁。

我们首先来看一个 synchronized 修饰方法的代码的例子:

public synchronized void method() {

    method body

}
复制代码

我们看到 method() 方法是被 synchronized 修饰的,为了方便理解其背后的原理,我们把上面这段代码改写为下面这种等价形式的伪代码。

public void method() {

    this.intrinsicLock.lock();

    try{

        method body

    }

    finally {

        this.intrinsicLock.unlock();

    }

}
复制代码

在这种写法中,进入 method 方法后,立刻添加内置锁,并且用 try 代码块把方法保护起来,最后用 finally 释放这把锁,这里的 intrinsicLock 就是 monitor 锁。经过这样的伪代码展开之后,相信你对 synchronized 的理解就更加清晰了。

用 javap 命令查看反汇编的结果

JVM 实现 synchronized 方法和 synchronized 代码块的细节是不一样的,下面我们就分别来看一下两者的实现。

同步代码块

首先我们来看下同步代码块的实现,如代码所示。

public class SynTest {

    public void synBlock() {

        synchronized (this) {

            System.out.println("lagou");

        }

    }

}
复制代码

在 SynTest 类中的 synBlock 方法,包含一个同步代码块,synchronized 代码块中有一行代码打印了 lagou 字符串,下面我们来通过命令看下 synchronized 关键字到底做了什么事情:首先用 cd 命令切换到 SynTest.java 类所在的路径,然后执行 javac SynTest.java,于是就会产生一个名为 SynTest.class 的字节码文件,然后我们执行 javap -verbose SynTest.class,就可以看到对应的反汇编内容。

关键信息如下:

  public void synBlock();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=3, args_size=1

         0: aload_0

         1: dup

         2: astore_1

         3: monitorenter

         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;

         7: ldc           #3                      // String lagou

         9: invokevirtual #4               // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        12: aload_1

        13: monitorexit

        14: goto          22

        17: astore_2

        18: aload_1

        19: monitorexit

        20: aload_2

        21: athrow

        22: return
复制代码

从里面可以看出,synchronized 代码块实际上多了 monitorenter 和 monitorexit 指令,标红的第3、13、19行指令分别对应的是 monitorenter 和 monitorexit。这里有一个 monitorenter,却有两个 monitorexit 指令的原因是,JVM 要保证每个 monitorenter 必须有与之对应的 monitorexit,monitorenter 指令被插入到同步代码块的开始位置,而 monitorexit 需要插入到方法正常结束处和异常处两个地方,这样就可以保证抛异常的情况下也能释放锁

可以把执行 monitorenter 理解为加锁,执行 monitorexit 理解为释放锁,每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,我们来具体看一下 monitorenter 和 monitorexit 的含义:

  • monitorenter

执行 monitorenter 的线程尝试获得 monitor 的所有权,会发生以下这三种情况之一:

a. 如果该 monitor 的计数为 0,则线程获得该 monitor 并将其计数设置为 1。然后,该线程就是这个 monitor 的所有者。

b. 如果线程已经拥有了这个 monitor ,则它将重新进入,并且累加计数。

c. 如果其他线程已经拥有了这个 monitor,那个这个线程就会被阻塞,直到这个 monitor 的计数变成为 0,代表这个 monitor 已经被释放了,于是当前这个线程就会再次尝试获取这个 monitor。

  • monitorexit monitorexit 的作用是将 monitor 的计数器减 1,直到减为 0 为止。代表这个 monitor 已经被释放了,已经没有任何线程拥有它了,也就代表着解锁,所以,其他正在等待这个 monitor 的线程,此时便可以再次尝试获取这个 monitor 的所有权。

同步方法

从上面可以看出,同步代码块是使用 monitorenter 和 monitorexit 指令实现的。而对于 synchronized 方法,并不是依靠 monitorenter 和 monitorexit 指令实现的,被 javap 反汇编后可以看到,synchronized 方法和普通方法大部分是一样的,不同在于,这个方法会有一个叫作 ACC_SYNCHRONIZED 的 flag 修饰符,来表明它是同步方法。

同步方法的代码如下所示。

public synchronized void synMethod() {

}
复制代码

对应的反汇编指令如下所示。

  public synchronized void synMethod();

    descriptor: ()V

    flags: ACC_PUBLIC, ACC_SYNCHRONIZED

    Code:

      stack=0, locals=1, args_size=1

         0: return

      LineNumberTable:

        line 16: 0
复制代码

可以看出,被 synchronized 修饰的方法会有一个 ACC_SYNCHRONIZED 标志。当某个线程要访问某个方法的时候,会首先检查方法是否有 ACC_SYNCHRONIZED 标志,如果有则需要先获得 monitor 锁,然后才能开始执行方法,方法执行之后再释放 monitor 锁。其他方面, synchronized 方法和刚才的 synchronized 代码块是很类似的,例如这时如果其他线程来请求执行方法,也会因为无法获得 monitor 锁而被阻塞。

好了,本文的内容就全部讲完了,我们讲解了获取和释放 monitor 的时机,以及被 synchronized 修饰的等价代码,然后我们还利用 javac 和 javap 命令查看了 synchronized 代码块以及 synchronized 方法所对应的的反汇编指令,其中同步代码块是利用 monitorenter 和 monitorexit 指令实现的,而同步方法则是利用 flags 实现的。


原文链接:
https://juejin.cn/post/7140204404908490788

相关推荐

每天一个AI姬,AMD核显用户有福了,AI绘画打破 NVIDIA 显卡垄断

使用StableDiffusion进行AI绘画,并不一定只能使用NVIDIA英伟达显卡,甚至,也不一定只能使用独立显卡。今天我们使用AMD6800H核显,并安装了StableDif...

NETworkManager:功能强大的网络管理与问题排除工具

关于NETworkManagerNETworkManager是一款功能强大的网络管理与问题排除工具,该工具完全开源,可以帮助广大研究人员轻松管理目标网络系统并排除网络疑难问题。该工具使用远程桌面、Po...

AMD也能深度学习+免费AI绘画:StableDiffusion+ROCm部署教程!

某国政客扇扇嘴皮子,CN玩硬件和深度学习的圈子里就掀起了一场风暴,这就是著名的嘴皮子效应(误)。没了高性能计算的A100H100倒也能理解,但是美利坚这波把RTX4090禁售了就让人无语了,所以不少做...

windows 下编译 python_rtmpstream

最近在研究数字人,看了大咖的项目(https://github.com/lipku/metahuman-stream),尝试编译此项目的依赖项目python_rtmpstream(https://gi...

如何使用 Python 操作 Git 代码?GitPython 入门介绍

花下猫语:今天,我在查阅如何用Python操作Gitlab的时候,看到这篇文章,觉得还不错,特分享给大家。文中还提到了其它几种操作Git的方法,后续有机会的话,再陆续分享之~~作者:匿蟒...

网上看了不少,终于把ZlmediaKit流媒体框架搭建起来啦

你都站在2023年代了,视频通话、视频直播、视频会议、视频监控就是风口浪尖上的猪师兄,只要你学那么一丁点,拿个高薪的工作不过分吧!我也是半瓶子晃荡的,所以路人呀,共学习,同进步!本篇开始,只讲在Lin...

MacDown:一款 macOS 的强大 Markdown 编辑器

大家好,很高兴又见面了,我是"...

ZLMediaKit安装配置和推拉流

一、ZLMediaKit库简介ZLMediaKit是一个基于...

大神赞过的:学习 WebAssembly 汇编语言程序设计

文/阿里淘系F(x)Team-旭伦随着前端页面变得越来越复杂,javascript的性能问题一再被诟病。而Javascript设计时就不是为了性能优化设计的,这使得浏览器上可以运行的本地语言一...

【Docker】部署WVP视频监控平台

回来Docker系列,今天将会跟大家分享一则关于开源WVP视频监控平台的搭建。先说结论吧,一开始按照网上说的一步一步搭建没有搭建成功,不知道是版本太旧还是我这边机器有问题,尝试了好几个不同方式的搭建都...

MongoDB+GridFS存储文件方案

GridFS是MongoDB的一个内置功能,它提供一组文件操作的API以利用MongoDB存储文件,GridFS的基本原理是将文件保存在两个Collection中,一个保存文件索引,一个保存文...

【开源】强大、创新且直观的 EDA套件

今天分享的LibrePCB是...

Ollama如何制作自己的大模型?

背景Llama3发布了,这次用了...

Ollama使用指南【超全版】

一、Ollama快速入门Ollama是一个用于在本地运行大型语言模型的工具,下面将介绍如何在不同操作系统上安装和使用Ollama。官网:https://ollama.comGithub:http...

基于区块链的价值共享互联网即时通讯应用平台源码免费分享

——————关注转发之后私信回复【源码】即可免费获取到本项目所有源码基于区块链的价值共享互联网即时通讯应用平台,是一个去中心化的任何人都可以使用的通讯网络,是一款基于区块链的价值共享互联网即时通讯AP...