Java并发编程中的基础概念Monitor

你好,这里是codetrend专栏"高并发编程基础"。

引言

在Java并发编程中,Monitor(监视器)是一种同步机制,用于实现线程间的互斥访问和共享资源的同步。它是一种基本的并发控制原语,在Java中以对象的形式存在。

每个Java对象都有一个与之关联的Monitor,可以通过synchronized关键字来使用该Monitor。当一个线程获得了一个对象的Monitor锁时,它就可以进入临界区,执行对应的同步代码块或方法。其他线程必须等待该Monitor的锁释放后才能进入临界区。

Monitor提供了以下核心概念:

  1. 锁定(Locking):一个Monitor只能被一个线程锁定,其他线程需要等待锁的释放才能继续执行。
  2. 互斥性(Mutual Exclusion):在任意时刻,只能有一个线程执行Monitor中的同步代码,并保证线程间的互斥访问。
  3. 条件变量(Condition Variables):Monitor可以使用条件变量来实现线程间的协作与通信。通过wait()方法释放锁并等待条件满足,通过notify()或notifyAll()方法唤醒等待的线程。
  4. 线程调度(Thread Scheduling):Monitor中的线程遵循一定的调度规则,例如公平锁会按照先后顺序唤醒等待的线程。

Monitor在Java中被广泛应用于同步代码块、同步方法、管程等场景,用于保护共享资源的访问,避免并发访问的竞态条件和数据不一致问题。通过使用Monitor,可以实现线程安全和正确的多线程编程。

Java对象头

Java对象头(Object Header)是Java虚拟机中用于管理对象的一部分元数据,存储在每个Java对象的内存中。它包含了一些重要的信息,用于支持对象的运行时特性和垃圾回收。

Java对象头通常占用一个机器字(Word)大小,具体大小取决于虚拟机的实现和运行环境。

Java对象头中包含的信息可以分为两类:

  1. Mark Word(标记字段):Mark Word是对象头中最重要的字段,用于存储对象的运行时数据和锁相关的信息。它可以包含以下内容:

    • 对象的哈希码(HashCode):用于快速比较对象是否相等。
    • 锁状态标志:记录对象的锁状态,比如无锁、偏向锁、轻量级锁、重量级锁等。
    • GC标记位:用于标记对象是否被垃圾回收器访问过。
    • 偏向锁的线程ID:当对象被偏向锁锁定时,记录拥有偏向锁的线程ID。
    • 偏向时间戳:记录上次成功获取偏向锁的时间戳。
  2. 类型指针(Klass Pointer):类型指针指向对象的类元数据,用于确定对象的类型信息,包括方法表、字段表等。通过类型指针,虚拟机可以确定对象的具体类型,并进行动态分派。

Java对象头在内存中紧跟在对象的地址之后,它是实现Java虚拟机的关键组成部分。对象头的布局和具体内容可能因不同的虚拟机实现而有所差异,但它们都具有类似的作用,用于支持对象的运行时特性、锁机制和垃圾回收。

Monitor对象

每个 Java 对象都可以关联一个 Monitor 对象,这是通过对象头中的 Mark Word 实现的。在对象头中,Mark Word 可以存储一些运行时数据和锁相关的信息,其中一个重要的信息就是指向 Monitor 对象的指针。当我们使用 synchronized 给对象上锁时,该对象的 Mark Word 就会被设置为指向 Monitor 对象的指针,这样就将对象与 Monitor 对象关联了起来。

这种关联方式是基于"重量级锁"的实现方式。在 Java 中,synchronized 关键字的实现有两种方式:轻量级锁和重量级锁。轻量级锁是一种基于对象头中的 CAS 操作实现的锁,用于提高并发性能。但是,当轻量级锁无法满足锁的需求时,就会升级为重量级锁。重量级锁是一种基于 Monitor 对象实现的锁,它可以保证多线程之间的互斥访问和同步执行。

在重量级锁的实现中,每个 Monitor 对象都与一个互斥锁相关联,用于控制对共享资源的访问。当一个线程尝试获得一个被锁定的对象时,它就会阻塞,并且该对象的 Mark Word 中的锁状态标志会被设置为"重量级锁"。此时,该对象就不再使用轻量级锁了,而是使用基于 Monitor 对象实现的重量级锁。同时,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,将对象与 Monitor 对象关联起来。

因此,Java 对象头中的 Mark Word 和 Monitor 对象是实现 Java 中锁机制的两个关键元素。它们共同保证了多线程之间的互斥访问和同步执行,确保了程序的正确性和安全性。在多线程编程中,需要充分理解对象头和 Monitor 对象的作用,合理地使用 synchronized 关键字,才能编写出高效、安全和正确的多线程程序。

graph LR A(Java对象) --> B[对象头Object Header] B --> C[Mark Word] B --> D[Monitor对象指针]

编程示例

示例代码如下:

java 复制代码
package engineer.concurrent.battle.fcontact;

public class MonitorObjectTest {
    static final Object lock = new Object();
    static int counter = 0;
    public static void main(String[] args) {
        synchronized (lock) {
            counter++;
            System.out.println(counter);
        }
    }
}

通过 javap -c MonitorObjectTest 输出字节码如下:

shell 复制代码
public class engineer.concurrent.battle.fcontact.MonitorObjectTest {
  static final java.lang.Object lock;

  static int counter;

  public engineer.concurrent.battle.fcontact.MonitorObjectTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field lock:Ljava/lang/Object;
       3: dup
       4: astore_1
       5: monitorenter                      // 将 lock对象 MarkWord 置为 Monitor 指针
       6: getstatic     #13                 // Field counter:I
       9: iconst_1
      10: iadd
      11: putstatic     #13                 // Field counter:I
      14: getstatic     #17                 // Field java/lang/System.out:Ljava/io/PrintStream;
      17: getstatic     #13                 // Field counter:I
      20: invokevirtual #23                 // Method java/io/PrintStream.println:(I)V
      23: aload_1
      24: monitorexit                        // 将 lock对象 MarkWord 重置, 唤醒 EntryList
      25: goto          33
      28: astore_2
      29: aload_1
      30: monitorexit
      31: aload_2
      32: athrow
      33: return
    Exception table:
       from    to  target type
           6    25    28   any
          28    31    28   any

  static {};
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: putstatic     #7                  // Field lock:Ljava/lang/Object;
      10: iconst_0
      11: putstatic     #13                 // Field counter:I
      14: return
}

通过字节码可以发现 锁开始会执行monitorenter,锁结束会执行monitorexit。

关于作者

来自一线全栈程序员nine的探索与实践,持续迭代中。

欢迎关注公众号"雨林寻北"或添加个人卫星codetrend(备注技术)。

相关推荐
IT学长编程1 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码1 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
郭二哈2 小时前
C++——模板进阶、继承
java·服务器·c++
A尘埃2 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23072 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
沉登c2 小时前
幂等性接口实现
java·rpc
代码之光_19802 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
科技资讯早知道3 小时前
java计算机毕设课设—坦克大战游戏
java·开发语言·游戏·毕业设计·课程设计·毕设
小比卡丘4 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言