在前面的文章中,我们已经体验过synchronized的用法,并对锁的概念和原理做了简单的介绍。然而,你可能已经察觉到,有一个概念似乎总是和synchronized这两个概念如影相随,很多人也比较喜欢问它们之间的区别,这个概念就是Monitor,也叫监视器

所以,在讲解完synchronized、锁之后,文本将为你讲解Monitor,揭示它们之间那些公开的秘密,希望你不再迷惑。

首先,你要明白的是,Monitor作为一种同步机制,它并非Java所特有,但Java实现了这一机制。

为了具象地理解Monitor这一抽象概念,我们先来分析身边的一个常见场景。

一、从医院排队就诊机制理解Monitor

相信你一定有过去医院就诊的经历。我们去医院时,情况一般是这样的:

  • 首先,我们在门诊大厅前台或自助挂号机进行挂号
  • 随后,挂号结束后我们找到对应的诊室就诊
    • 诊室每次只能有一个患者就诊;
    • 如果此时诊室空闲,直接进入就诊;
    • 如果此时诊室内有患者正在就诊,那么我们进入候诊室,等待叫号;
  • 就诊结束后,走出就诊室,候诊室的下一位候诊患者进入就诊室。

这个就诊过程你一定耳熟能详,理解起来必然毫不费力气。我们做了一张图展示图下:

仔细看这幅图中的就诊过程,如果你理解了这个过程,你就理解了Monitor. 这么简单吗?不要怀疑自己,是的。你竟然早已理解Monitor机制

不要小看这个机制,它可是生活中的智慧体现。在这个就诊机制中,它起到了两个关键性的作用

  • 互斥(mutual exclusion ):每次只允许一个患者进入候诊室就诊;
  • 协作(cooperation):就诊室中的患者就诊结束后,可以通知候诊区的下一位患者。

明白了吗?你在医院就诊的过程竟然和Monitor的机制几乎一模一样。我们换个方式来描述Monitor在计算机科学中的作用:

  • 互斥(mutual exclusion ):每次只允许一个线程进入临界区;
  • 协作(cooperation):当临界区的线程执行结束后满足特定条件时,可以通知其他的等待线程进入。

而就诊过程中的门诊大厅就诊室候诊室则恰好对应着Monitor中的三个关键概念。其中:

  • 门诊大厅:所有待进入的线程都必须先在入口(Entry Set)挂号才有资格;
  • 就诊室:一个每次只能有一个线程进入的特殊房间(Special Room)
  • 候诊室:就诊室繁忙时,进入等待区(Wait Set)

我们把上面的图稍作调整,就可以看到Monitor在Java中的模样:

对比来看,相信你已经很直观地理解Monitor机制。再一回味,你会发现synchronized正是对Monitor机制的一种实现。而在Java中,每一个对象都会关联一个监视器

二、从synchronized源码感受Monitor

既然synchronized是对Monitor机制的一种实现,为了让你更有体感,我们可以写一段极简代码一探究竟。

这段代码极为简单,但是够用,我们在代码中使用了synchronized关键字:

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

代码写好后,分别执行javac SyncMonitorDemo.javajavap -v SyncMonitorDemo.class,随后你就能得到下面这样的字节码:

Classfile SyncMonitorDemo.class
Last modified May 26, 2021; size 684 bytes
MD5 checksum e366920f22845e98c45f26531596d6cf
Compiled from "SyncMonitorDemo.java"
public class cn.tao.king.juc.execises1.SyncMonitorDemo
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // java/lang/Object
#3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #26 // locking...
#5 = Methodref #27.#28 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #29 // cn/tao/king/juc/execises1/SyncMonitorDemo
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcn/tao/king/juc/execises1/SyncMonitorDemo;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 o
#19 = Utf8 Ljava/lang/Object;
#20 = Utf8 SourceFile
#21 = Utf8 SyncMonitorDemo.java
#22 = NameAndType #7:#8 // "<init>":()V
#23 = Utf8 java/lang/Object
#24 = Class #30 // java/lang/System
#25 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#26 = Utf8 locking...
#27 = Class #33 // java/io/PrintStream
#28 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#29 = Utf8 cn/tao/king/juc/execises1/SyncMonitorDemo
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public cn.tao.king.juc.execises1.SyncMonitorDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/tao/king/juc/execises1/SyncMonitorDemo; 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 locking...
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
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable:
line 5: 0
line 6: 8
line 7: 12
line 8: 20
line 9: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 o Ljava/lang/Object;
}
SourceFile: "SyncMonitorDemo.java"

javap是JDK自带的一个反汇编命令。你可以忽略其他不必要的信息,直接在结果中找到下面这段代码:

11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String locking...
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit

看到monitorentermonitorexit指令,相信智慧的你已经看穿一切。

以上就是文本的全部内容,恭喜你又上了一颗星

夫子的试炼

  • 写一段包含synchronized关键字的代码,使用javap命令观察结果。

关于作者

关注公众号【庸人技术笑谈】,获取及时文章更新。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不兜售课程。

如果本文对你有帮助,欢迎点赞关注

并发王者课-青铜7:顺藤摸瓜-如何从synchronized中的锁认识Monitor的更多相关文章

  1. 并发王者课 - 青铜4:synchronized用法初体验

    在前面的文章<双刃剑-理解多线程带来的安全问题>中,我们提到了多线程情况下存在的线程安全问题.本文将以这个问题为背景,介绍如何通过使用synchronized关键字解这一问题.当然,在青铜 ...

  2. 并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁

    在前面的文章<青铜4:synchronized用法初体验>中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现.可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是 ...

  3. 并发王者课 - 青铜 2:峡谷笔记 - 简单认识Java中的线程

    在前面的<兵分三路:如何创建多线程>文章中,我们已经通过Thread和Runnable直观地了解如何在Java中创建一个线程,相信你已经有了一定的体感.在本篇文章中,我们将基于前面的示例代 ...

  4. 并发王者课-铂金1:探本溯源-为何说Lock接口是Java中锁的基础

    欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...

  5. 并发王者课-铂金2:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言

    欢迎来到<并发王者课>,本文是该系列文章中的第15篇. 在上篇文章中,我们介绍了Java中锁的基础Lock接口.在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWri ...

  6. 并发王者课-铂金6:青出于蓝-Condition如何把等待与通知玩出新花样

    欢迎来到<[并发王者课](https://juejin.cn/post/6967277362455150628)>,本文是该系列文章中的**第19篇**. 在上一篇文章中,我们介绍了阻塞队 ...

  7. 并发王者课-铂金8:峡谷幽会-看CyclicBarrier如何跨越重峦叠嶂

    欢迎来到<并发王者课>,本文是该系列文章中的第21篇,铂金中的第8篇. 在上一篇文章中,我们介绍了CountDownLatch的用法.在协调多线程的开始和结束时,CountDownLatc ...

  8. 并发王者课-铂金9:互通有无-Exchanger如何完成线程间的数据交换

    欢迎来到<并发王者课>,本文是该系列文章中的第22篇,铂金中的第9篇. 在前面的文章中,我们已经介绍了ReentrantLock,CountDownLatch,CyclicBarrier, ...

  9. 并发王者课-铂金10:能工巧匠-ThreadLocal如何为线程打造私有数据空间

    欢迎来到<并发王者课>,本文是该系列文章中的第23篇,铂金中的第10篇. 说起ThreadLocal,相信你对它的名字一定不陌生.在并发编程中,它有着较高的出场率,并且也是面试中的高频面试 ...

随机推荐

  1. Docker下MySQL的安装

    1 概述 本文讲述了如何利用Docker去安装MySQL,以及MySQL自定义配置文件的相关设置. 2 安装Docker 首先安装Docker并开启服务: systemctl start docker ...

  2. ASP.NET Core扩展库之Http请求模拟

    如今,完全独立的业务应用几乎不存在,不管是在企业内部微服务之间的调用,还是与外部第三方服务的调用,Http的API交互是常见的场景,这些实际情况给我们的开发带来了比较大的挑战,一是第三方服务可能会牵制 ...

  3. 另类的曲线方式定时Start up/Shut down VM 的解决方案

    一,引言 最近看到一位小兄弟在为了做 Azure 云虚拟机的自动关机开启 在群里求助,最后也不知道结果咋样了. 至于他提到的利用 Automation Account 我是没有接触过,并且也没有看资料 ...

  4. php中var关键字用法

    见很多朋友说在php中定义变量用不用var都没关系,其实不然. 看看例子,如果我这样使用var: 1 var $a=123; 2 echo $a; 3 //那么程序会提示语法错误,要去掉var这个变量 ...

  5. 【面试题2020-03-30】面试官:对并发熟悉吗?说说Synchronized及实现原理

    一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法. Synchronized的作用主要有三个: 1.确保线程互斥的访问同 ...

  6. hdu2167 方格取数 状态压缩dp

    题意:      方格取数,八个方向的限制. 思路:      八个方向的不能用最大流了,四个的可以,八个的不能抽象成二分图,所以目测只能用dp来跑,dp[i][j]表示的是第i行j状态的最优,具体看 ...

  7. URL中字符的含义

    目录 URL中的特殊字符 #号的作用 URL中的特殊字符 我们经常会看到URL中有一些特殊的字符,比如 #,?,& ,/ ,+ 等.那么,它们的含义是什么呢? 特殊含义 十六进制 + 表示空格 ...

  8. 通过使用 NTLite 工具实现精简Windows系统

    NTLite 是一款专业于Windows平台的系统精简工具,NTLite主要面对系统封装人员使用,比如各大下载站及GHO镜像下载站,Windows系统二次精简封装打包使用,NTLite可以对系统进行极 ...

  9. Win64 驱动内核编程-32.枚举与删除注册表回调

    枚举与删除注册表回调 注册表回调是一个监控注册表读写的回调,它的效果非常明显,一个回调能实现在SSDT 上 HOOK 十几个 API 的效果.部分游戏保护还会在注册表回调上做功夫,监控 service ...

  10. Andrew Ng机器学习算法入门((六):多变量线性回归方程求解

    多变量线性回归 之前讨论的都是单变量的情况.例如房价与房屋面积之前的关系,但是实际上,房价除了房屋面积之外,还要房间数,楼层等因素相关.那么此时就变成了一个多变量线性回归的问题.在实际问题中,多变量的 ...