在前面的文章中,我们已经体验过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. Java集合知识总结

    集合概述 集合:集合是Java中提供的一种容器,可以用来存储多个数据. 集合和数组的区别: (1)数组长度的是固定的,集合的长度是可变的. (2)数组中存储的都是同一类型的元素.集合存储的都是对象,对 ...

  2. shopify 学习链接整理

    shopify shopify packagist https://help.shopify.com/zh-CN/manual/apps/apps-by-shopify/script-editor/s ...

  3. 【docker-compose】docker-compose环境安装

    docker-compose: 是一个用于定义和运行多容器 Docker 的应用程序工具,可以帮助我们可以轻松.高效的管理容器 安装: 1.安装pip 工具-目的是为了下载docker-compose ...

  4. 【小白向】基于Docker使用Gogs,Drone以及drone-runner-docker的自动化部署

    Gogs是基于Go语言编写的可以替代gitlab的代码托管平台,它没有gitlab那么庞大且不需要占用大量资源,对小型服务器相对于比较友好,我们甚至可以使用树莓派搭建. 服务器配置: * 操作系统:C ...

  5. Hook android系统调用的实践

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/71037182 一.环境条件 Ubuntukylin 14.04.5 x64bit ...

  6. Android Hook框架adbi的分析(3)---编译和inline Hook实践

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75200800 一.序言 在前面的博客中,已经分析过了Android Hook框架a ...

  7. hdu4291 暴力循环节+矩阵快速幂

    题意:       给你一个关系式,x[n] = 3*x[n-1] + x[n-2],求x(x(x[n]))%1000000007. 思路:       做这个题目要明确一点,就是对于取余操作大多数时 ...

  8. 堆栈上的舞蹈之释放重引用(UAF) 漏洞原理实验分析

    0x01 前言 释放重引用的英文名名称是 Use After Free,也就是著名的 UAF 漏洞的全称.从字面意思可以看出 After Free 就是释放后的内存空间,Use 就是使用的意思,使用释 ...

  9. Shiro反序列化漏洞复现

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.使用Shiro的易于理解的API,可以快速.轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企 ...

  10. WPF之事件绑定命令

    目录 事件绑定意义 无参数的事件绑定 带EventArgs参数的事件绑定 使用事件绑定 扩展:基于InvokeCommandAction源码的实现(推荐) 参考资料 事件绑定意义 一般事件的处理程序都 ...